2
0

Compare commits

...

12 Commits

Author SHA1 Message Date
Maarten Billemont
f21d0f7cfc [maven-release-plugin] prepare release 2.4-java 2016-02-20 22:29:27 -05:00
Maarten Billemont
7eb10cb5a6 Fix a bug with storing the default algorithm version. 2016-02-20 22:26:50 -05:00
Maarten Billemont
64829c99d8 Initial support for security questions in Mac app. 2016-02-20 21:56:04 -05:00
Maarten Billemont
0269c2741a Silence warning when releasing about jarsigner version ambiguity. 2016-02-20 21:53:58 -05:00
Maarten Billemont
f5638ea798 A little clean-up. 2016-02-20 21:48:18 -05:00
Maarten Billemont
6a1768a50d Fixed a few bugs. 2016-02-20 21:45:11 -05:00
Maarten Billemont
b346b3be65 Finish up Android UI improvements. 2016-02-20 21:27:59 -05:00
Maarten Billemont
060ec0b5cd Move preferences into a global preferences controller. 2016-02-20 20:30:08 -05:00
Maarten Billemont
7f8a36e32e Added ability to switch from native to java-only KDF. 2016-02-20 18:50:44 -05:00
Maarten Billemont
024899f311 Updated and fixed encoding of a few PNGs. 2016-02-20 18:49:21 -05:00
Maarten Billemont
6ffef78469 Fix some C warnings. 2016-02-19 08:34:06 -05:00
Maarten Billemont
b574158d92 Update logback layout to new format. 2016-02-19 08:25:14 -05:00
92 changed files with 745 additions and 324 deletions

View File

@@ -5,7 +5,6 @@
<inspection_tool class="Convert to string" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MethodIsLaterInTheScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />

View File

@@ -8,6 +8,7 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"

View File

@@ -8,6 +8,7 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"

View File

@@ -8,6 +8,7 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"

View File

@@ -8,6 +8,7 @@
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h"
#include "mpw-util.h"

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</parent>
<name>Master Password Algorithm Implementation</name>

View File

@@ -16,12 +16,12 @@ import org.jetbrains.annotations.Contract;
*/
public enum MPSiteType {
GeneratedMaximum( "20 characters, contains symbols.", //
GeneratedMaximum( "Max", "20 characters, contains symbols.", //
ImmutableList.of( "x", "max", "maximum" ), //
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
MPSiteTypeClass.Generated, 0x0 ),
GeneratedLong( "Copy-friendly, 14 characters, contains symbols.", //
GeneratedLong( "Long", "Copy-friendly, 14 characters, contains symbols.", //
ImmutableList.of( "l", "long" ), //
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
@@ -36,49 +36,50 @@ public enum MPSiteType {
new MPTemplate( "CvccCvcvCvccno" ) ), //
MPSiteTypeClass.Generated, 0x1 ),
GeneratedMedium( "Copy-friendly, 8 characters, contains symbols.", //
GeneratedMedium( "Medium", "Copy-friendly, 8 characters, contains symbols.", //
ImmutableList.of( "m", "med", "medium" ), //
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), //
MPSiteTypeClass.Generated, 0x2 ),
GeneratedBasic( "8 characters, no symbols.", //
GeneratedBasic( "Basic", "8 characters, no symbols.", //
ImmutableList.of( "b", "basic" ), //
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), //
MPSiteTypeClass.Generated, 0x3 ),
GeneratedShort( "Copy-friendly, 4 characters, no symbols.", //
GeneratedShort( "Short", "Copy-friendly, 4 characters, no symbols.", //
ImmutableList.of( "s", "short" ), //
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
MPSiteTypeClass.Generated, 0x4 ),
GeneratedPIN( "4 numbers.", //
GeneratedPIN( "PIN", "4 numbers.", //
ImmutableList.of( "i", "pin" ), //
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
MPSiteTypeClass.Generated, 0x5 ),
GeneratedName( "9 letter name.", //
GeneratedName( "Name", "9 letter name.", //
ImmutableList.of( "n", "name" ), //
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
MPSiteTypeClass.Generated, 0xE ),
GeneratedPhrase( "20 character sentence.", //
GeneratedPhrase( "Phrase", "20 character sentence.", //
ImmutableList.of( "p", "phrase" ), //
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
MPSiteTypeClass.Generated, 0xF ),
StoredPersonal( "AES-encrypted, exportable.", //
StoredPersonal( "Personal", "AES-encrypted, exportable.", //
ImmutableList.of( "personal" ), //
ImmutableList.<MPTemplate>of(), //
MPSiteTypeClass.Stored, 0x0, MPSiteFeature.ExportContent ),
StoredDevicePrivate( "AES-encrypted, not exported.", //
StoredDevicePrivate( "Device", "AES-encrypted, not exported.", //
ImmutableList.of( "device" ), //
ImmutableList.<MPTemplate>of(), //
MPSiteTypeClass.Stored, 0x1, MPSiteFeature.DevicePrivate );
static final Logger logger = Logger.get( MPSiteType.class );
private final String shortName;
private final String description;
private final List<String> options;
private final List<MPTemplate> templates;
@@ -86,9 +87,10 @@ public enum MPSiteType {
private final int typeIndex;
private final Set<MPSiteFeature> typeFeatures;
MPSiteType(final String description, final List<String> options, final List<MPTemplate> templates, final MPSiteTypeClass typeClass,
final int typeIndex, final MPSiteFeature... typeFeatures) {
MPSiteType(final String shortName, final String description, final List<String> options, final List<MPTemplate> templates,
final MPSiteTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
this.shortName = shortName;
this.description = description;
this.options = options;
this.templates = templates;
@@ -102,6 +104,10 @@ public enum MPSiteType {
this.typeFeatures = typeFeaturesBuilder.build();
}
public String getShortName() {
return shortName;
}
public String getDescription() {
return description;

View File

@@ -17,9 +17,11 @@ public abstract class MasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKey.class );
private static boolean allowNativeByDefault = true;
@Nonnull
private final String fullName;
private boolean allowNative = allowNativeByDefault;
@Nullable
private byte[] masterKey;
@@ -46,6 +48,23 @@ public abstract class MasterKey {
throw new UnsupportedOperationException( "Unsupported version: " + version );
}
public static boolean isAllowNativeByDefault() {
return allowNativeByDefault;
}
/**
* Native libraries are useful for speeding up the performance of cryptographical functions.
* Sometimes, however, we may prefer to use Java-only code.
* For instance, for auditability / trust or because the native code doesn't work on our CPU/platform.
* <p/>
* This setter affects the default setting for any newly created {@link MasterKey}s.
*
* @param allowNative false to disallow the use of native libraries.
*/
public static void setAllowNativeByDefault(final boolean allowNative) {
allowNativeByDefault = allowNative;
}
protected MasterKey(@NotNull final String fullName) {
this.fullName = fullName;
@@ -63,6 +82,15 @@ public abstract class MasterKey {
return fullName;
}
public boolean isAllowNative() {
return allowNative;
}
public MasterKey setAllowNative(final boolean allowNative) {
this.allowNative = allowNative;
return this;
}
@Nonnull
protected byte[] getKey() {

View File

@@ -65,8 +65,15 @@ public class MasterKeyV0 extends MasterKey {
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
return scrypt( masterKeySalt, mpBytes );
}
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
try {
if (isAllowNative())
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
else
return SCrypt.scryptJ( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );

View File

@@ -46,17 +46,8 @@ public class MasterKeyV3 extends MasterKeyV2 {
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 );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
return null;
}
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
return scrypt( masterKeySalt, mpBytes );
}
}

View File

@@ -11,14 +11,15 @@
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:allowBackup="true">
<activity android:name=".TestActivity" android:theme="@style/MPTheme">
android:allowBackup="true"
android:debuggable="true">
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme" />
<activity android:name=".TestActivity" android:theme="@style/MPTheme" />
</application>
</manifest>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</parent>
<name>Master Password Android</name>
@@ -67,6 +67,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>signing</id>
@@ -104,13 +105,13 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</dependency>
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-tests</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
@@ -17,6 +18,13 @@
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
android:src="@drawable/img_identity" />
<EditText
android:id="@+id/fullNameField"
android:layout_width="match_parent"
@@ -26,14 +34,14 @@
android:hint="@string/fullName_hint"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="26sp" />
android:textSize="16sp" />
<CheckBox
android:id="@+id/rememberFullNameField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/rememberPasswordField"
android:textSize="14sp"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/remember" />
@@ -46,16 +54,24 @@
android:hint="@string/masterPassword_hint"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="18sp" />
android:textSize="16sp" />
<CheckBox
android:id="@id/rememberPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/forgetOnClose" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
android:src="@drawable/img_key" />
<EditText
android:id="@id/siteNameField"
android:layout_width="match_parent"
@@ -65,7 +81,7 @@
android:hint="@string/siteName_hint"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="18sp" />
android:textSize="16sp" />
<FrameLayout
android:layout_width="match_parent"
@@ -76,8 +92,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:indeterminate="true" />
android:visibility="invisible"
android:indeterminate="true"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
@@ -89,12 +106,12 @@
android:id="@id/sitePasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteTypeField"
android:nextFocusForward="@+id/siteTypeButton"
android:gravity="center"
android:background="@android:color/transparent"
android:textColor="#FFFFFF"
android:textSize="32sp"
android:text="LuxdZozvDuma4["
android:textSize="28sp"
tools:text="LuxdZozvDuma4["
android:onClick="copySitePassword" />
<TextView
@@ -104,7 +121,7 @@
android:labelFor="@id/sitePasswordField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/sitePassword_hint" />
@@ -115,55 +132,137 @@
android:id="@+id/maskPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/maskPassword" />
<Spinner
android:id="@id/siteTypeField"
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/counterField"
android:gravity="center" />
android:layout_marginTop="20dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
android:src="@drawable/divider" />
<EditText
android:id="@id/counterField"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteVersionField"
style="?android:buttonBarStyle"
android:orientation="horizontal"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@id/siteTypeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
style="?android:buttonBarButtonStyle"
android:nextFocusForward="@+id/counterField"
android:gravity="center"
android:inputType="text|textNoSuggestions"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:text="1" />
android:textSize="16sp"
android:drawableStart="@drawable/icon_key"
android:drawablePadding="8dp"
android:background="@android:color/transparent"
tools:text="Long" />
<TextView
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@id/counterField"
android:labelFor="@id/siteTypeButton"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteType_hint" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@id/counterField"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
style="?android:buttonBarButtonStyle"
android:nextFocusForward="@+id/siteVersionButton"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:drawableStart="@drawable/icon_plus"
android:drawablePadding="8dp"
android:background="@android:color/transparent"
tools:text="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@id/siteVersionButton"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteCounter_hint" />
<Spinner
android:id="@id/siteVersionField"
android:layout_width="match_parent"
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:nextFocusForward="@id/rememberFullNameField"
android:gravity="center" />
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@id/siteVersionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
style="?android:buttonBarButtonStyle"
android:nextFocusForward="@+id/rememberFullNameField"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:drawableStart="@drawable/icon_gears"
android:drawablePadding="8dp"
android:background="@android:color/transparent"
tools:text="3" />
<TextView
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@id/siteVersionField"
android:labelFor="@id/siteVersionButton"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteVersion_hint" />
</LinearLayout>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:text="@string/btn_tests"
android:onClick="integrityTests"
android:background="@android:color/transparent" />
<View
android:layout_width="1dp"
android:layout_height="0dp"

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
@@ -17,22 +18,31 @@
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:src="@drawable/img_stats" />
<ProgressBar
android:id="@+id/progressView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
style="@android:style/Widget.ProgressBar.Horizontal" />
tools:max="100"
tools:progress="80"
style="?android:progressBarStyleHorizontal" />
<TextView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/sitePasswordField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/secondary_text_dark"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/tests_testing" />
<TextView
@@ -53,8 +63,15 @@
android:layout_marginTop="8dp"
android:enabled="false"
android:text="@string/tests_btn_testing"
android:onClick="onAction"/>
android:onClick="onAction" />
<CheckBox
android:id="@+id/nativeKDFField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/nativeKDF" />
</LinearLayout>
</ScrollView>

View File

@@ -8,15 +8,18 @@
<string name="masterPassword_hint">Your master password</string>
<string name="siteName_hint">eg. google.com</string>
<string name="sitePassword_hint">Tap to copy</string>
<string name="siteCounter_hint">Password #</string>
<string name="siteType_hint">Type</string>
<string name="siteCounter_hint">Counter</string>
<string name="siteVersion_hint">Algorithm</string>
<string name="empty" />
<string name="btn_tests">Integrity Tests </string>
<string name="tests_unavailable">Test suite unavailable.</string>
<string name="tests_btn_unavailable">Exit</string>
<string name="tests_btn_unavailable">Retest</string>
<string name="tests_testing">Testing device\'s password generation integrity…</string>
<string name="tests_btn_testing">Please Stand By…</string>
<string name="tests_failed">Incompatible device or OS.</string>
<string name="tests_btn_failed">Exit</string>
<string name="tests_btn_failed">Retest</string>
<string name="tests_passed">Integrity checks passed!</string>
<string name="tests_btn_passed">Continue</string>
<string name="tests_btn_passed">Close</string>
<string name="nativeKDF">Use native key derivation</string>
</resources>

View File

@@ -1,6 +1,5 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.*;
@@ -17,9 +16,11 @@ import android.widget.*;
import butterknife.ButterKnife;
import butterknife.InjectView;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.*;
import javax.annotation.Nullable;
@@ -32,19 +33,10 @@ public class EmergencyActivity extends Activity {
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 Preferences preferences = Preferences.get( this );
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
@Override
void update() {
updateMasterKey();
}
};
private final ValueChangedListener updateSitePassword = new ValueChangedListener() {
@Override
void update() {
updateSitePassword();
}
};
private final ImmutableList<MPSiteType> allSiteTypes = ImmutableList.copyOf( MPSiteType.forClass( MPSiteTypeClass.Generated ) );
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() );
private ListenableFuture<MasterKey> masterKeyFuture;
@@ -60,14 +52,14 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.siteNameField)
EditText siteNameField;
@InjectView(R.id.siteTypeField)
Spinner siteTypeField;
@InjectView(R.id.siteTypeButton)
Button siteTypeButton;
@InjectView(R.id.counterField)
EditText counterField;
Button siteCounterButton;
@InjectView(R.id.siteVersionField)
Spinner siteVersionField;
@InjectView(R.id.siteVersionButton)
Button siteVersionButton;
@InjectView(R.id.sitePasswordField)
Button sitePasswordField;
@@ -84,8 +76,9 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.maskPasswordField)
CheckBox maskPasswordField;
private int hc_userName;
private int hc_masterPassword;
private int id_userName;
private int id_masterPassword;
private int id_version;
private String sitePassword;
public static void start(Context context) {
@@ -101,12 +94,58 @@ public class EmergencyActivity extends Activity {
setContentView( R.layout.activity_emergency );
ButterKnife.inject( this );
fullNameField.setOnFocusChangeListener( updateMasterKey );
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
siteNameField.addTextChangedListener( updateSitePassword );
siteTypeField.setOnItemSelectedListener( updateSitePassword );
counterField.addTextChangedListener( updateSitePassword );
siteVersionField.setOnItemSelectedListener( updateMasterKey );
fullNameField.setOnFocusChangeListener( new ValueChangedListener() {
@Override
void update() {
updateMasterKey();
}
} );
masterPasswordField.setOnFocusChangeListener( new ValueChangedListener() {
@Override
void update() {
updateMasterKey();
}
} );
siteNameField.addTextChangedListener( new ValueChangedListener() {
@Override
void update() {
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
updateSitePassword();
}
} );
siteTypeButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(final View v) {
@SuppressWarnings("SuspiciousMethodCalls")
MPSiteType siteType =
allSiteTypes.get( (allSiteTypes.indexOf( siteTypeButton.getTag() ) + 1) % allSiteTypes.size() );
preferences.setDefaultSiteType( siteType );
siteTypeButton.setTag( siteType );
siteTypeButton.setText( siteType.getShortName() );
updateSitePassword();
}
} );
siteCounterButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(final View v) {
UnsignedInteger counter =
UnsignedInteger.valueOf( siteCounterButton.getText().toString() ).plus( UnsignedInteger.ONE );
siteCounterButton.setText( MessageFormat.format( "{0}", counter ) );
updateSitePassword();
}
} );
siteVersionButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(final View v) {
@SuppressWarnings("SuspiciousMethodCalls")
MasterKey.Version siteVersion =
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
preferences.setDefaultVersion( siteVersion );
siteVersionButton.setTag( siteVersion );
siteVersionButton.setText( siteVersion.name() );
updateMasterKey();
}
} );
sitePasswordField.addTextChangedListener( new ValueChangedListener() {
@Override
void update() {
@@ -127,32 +166,26 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
siteTypeField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
siteTypeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
siteVersionField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MasterKey.Version.values() ) );
siteVersionField.setSelection( MasterKey.Version.CURRENT.ordinal() );
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberFullName", isChecked ).apply();
preferences.setRememberFullName( isChecked );
if (isChecked)
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullNameField.getText().toString() ).apply();
preferences.setFullName( fullNameField.getText().toString() );
else
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", "" ).apply();
preferences.setFullName( null );
}
} );
forgetPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "forgetPassword", isChecked ).apply();
preferences.setForgetPassword( isChecked );
}
} );
maskPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "maskPassword", isChecked ).apply();
preferences.setMaskPassword( isChecked );
sitePasswordField.setTransformationMethod( isChecked? new PasswordTransformationMethod(): null );
}
} );
@@ -162,13 +195,24 @@ public class EmergencyActivity extends Activity {
protected void onResume() {
super.onResume();
fullNameField.setText( getPreferences( MODE_PRIVATE ).getString( "fullName", "" ) );
rememberFullNameField.setChecked( isRememberFullNameEnabled() );
forgetPasswordField.setChecked( isForgetPasswordEnabled() );
maskPasswordField.setChecked( isMaskPasswordEnabled() );
sitePasswordField.setTransformationMethod( isMaskPasswordEnabled()? new PasswordTransformationMethod(): null );
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
if (TextUtils.isEmpty( masterPasswordField.getText() ))
fullNameField.setText( preferences.getFullName() );
rememberFullNameField.setChecked( preferences.isRememberFullName() );
forgetPasswordField.setChecked( preferences.isForgetPassword() );
maskPasswordField.setChecked( preferences.isMaskPassword() );
sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null );
MPSiteType defaultSiteType = preferences.getDefaultSiteType();
siteTypeButton.setTag( defaultSiteType );
siteTypeButton.setText( defaultSiteType.getShortName() );
MasterKey.Version defaultVersion = preferences.getDefaultVersion();
siteVersionButton.setTag( defaultVersion );
siteVersionButton.setText( defaultVersion.name() );
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
if (TextUtils.isEmpty( fullNameField.getText() ))
fullNameField.requestFocus();
else if (TextUtils.isEmpty( masterPasswordField.getText() ))
masterPasswordField.requestFocus();
else
siteNameField.requestFocus();
@@ -176,9 +220,9 @@ public class EmergencyActivity extends Activity {
@Override
protected void onPause() {
if (isForgetPasswordEnabled()) {
if (preferences.isForgetPassword()) {
synchronized (this) {
hc_userName = hc_masterPassword = 0;
id_userName = id_masterPassword = 0;
if (masterKeyFuture != null) {
masterKeyFuture.cancel( true );
masterKeyFuture = null;
@@ -195,35 +239,20 @@ public class EmergencyActivity extends Activity {
super.onPause();
}
private boolean isRememberFullNameEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "rememberFullName", false );
}
private boolean isForgetPasswordEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "forgetPassword", false );
}
private boolean isMaskPasswordEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "maskPassword", false );
}
private synchronized void updateMasterKey() {
final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
final MasterKey.Version version = (MasterKey.Version) siteVersionField.getSelectedItem();
try {
if (fullName.hashCode() == hc_userName && Arrays.hashCode( masterPassword ) == hc_masterPassword &&
masterKeyFuture != null && masterKeyFuture.get().getAlgorithmVersion() == version)
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
if (fullName.hashCode() == id_userName && Arrays.hashCode( masterPassword ) == id_masterPassword &&
version.ordinal() == id_version && masterKeyFuture != null && !masterKeyFuture.isCancelled())
return;
}
catch (InterruptedException | ExecutionException e) {
return;
}
hc_userName = fullName.hashCode();
hc_masterPassword = Arrays.hashCode( masterPassword );
if (isRememberFullNameEnabled())
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullName ).apply();
id_userName = fullName.hashCode();
id_masterPassword = Arrays.hashCode( masterPassword );
id_version = version.ordinal();
if (preferences.isRememberFullName())
preferences.setFullName( fullName );
if (masterKeyFuture != null)
masterKeyFuture.cancel( true );
@@ -243,7 +272,7 @@ public class EmergencyActivity extends Activity {
try {
return MasterKey.create( version, fullName, masterPassword );
}
catch (RuntimeException e) {
catch (Exception e) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
logger.err( e, "While generating master key." );
@@ -265,8 +294,8 @@ public class EmergencyActivity extends Activity {
private void updateSitePassword() {
final String siteName = siteNameField.getText().toString();
final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
final UnsignedInteger counter = UnsignedInteger.valueOf( ifNotNullElse( counterField.getText(), "1" ).toString() );
final MPSiteType type = (MPSiteType) siteTypeButton.getTag();
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
sitePasswordField.setText( "" );
@@ -313,6 +342,14 @@ public class EmergencyActivity extends Activity {
} );
}
public void integrityTests(View view) {
if (masterKeyFuture != null) {
masterKeyFuture.cancel( true );
masterKeyFuture = null;
}
TestActivity.startNoSkip( this );
}
public void copySitePassword(View view) {
final String currentSitePassword = this.sitePassword;
if (TextUtils.isEmpty( currentSitePassword ))

View File

@@ -0,0 +1,148 @@
package com.lyndir.masterpassword;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2016-02-20
*/
public class Preferences {
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
private static final String PREF_NATIVE_KDF = "nativeKDF";
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
private static final String PREF_MASK_PASSWORD = "maskPassword";
private static final String PREF_FULL_NAME = "fullName";
private static final String PREF_SITE_TYPE = "siteType";
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
private static Preferences instance;
private Context context;
@Nullable
private SharedPreferences prefs;
public static synchronized Preferences get(final Context context) {
if (instance == null)
instance = new Preferences( context );
return instance;
}
private Preferences(Context context) {
this.context = context;
}
@Nonnull
private SharedPreferences prefs() {
if (prefs == null)
prefs = (context = context.getApplicationContext()).getSharedPreferences( getClass().getCanonicalName(), Context.MODE_PRIVATE );
return prefs;
}
public boolean setNativeKDFEnabled(boolean enabled) {
if (isAllowNativeKDF() == enabled)
return false;
prefs().edit().putBoolean( PREF_NATIVE_KDF, enabled ).apply();
return true;
}
public boolean isAllowNativeKDF() {
return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() );
}
public boolean setTestsPassed(final Set<String> value) {
if (Sets.symmetricDifference( getTestsPassed(), value ).isEmpty())
return false;
prefs().edit().putStringSet( PREF_TESTS_PASSED, value ).apply();
return true;
}
public Set<String> getTestsPassed() {
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.<String>of() );
}
public boolean setRememberFullName(boolean enabled) {
if (isRememberFullName() == enabled)
return false;
prefs().edit().putBoolean( PREF_REMEMBER_FULL_NAME, enabled ).apply();
return true;
}
public boolean isRememberFullName() {
return prefs().getBoolean( PREF_REMEMBER_FULL_NAME, false );
}
public boolean setForgetPassword(boolean enabled) {
if (isForgetPassword() == enabled)
return false;
prefs().edit().putBoolean( PREF_FORGET_PASSWORD, enabled ).apply();
return true;
}
public boolean isForgetPassword() {
return prefs().getBoolean( PREF_FORGET_PASSWORD, false );
}
public boolean setMaskPassword(boolean enabled) {
if (isMaskPassword() == enabled)
return false;
prefs().edit().putBoolean( PREF_MASK_PASSWORD, enabled ).apply();
return true;
}
public boolean isMaskPassword() {
return prefs().getBoolean( PREF_MASK_PASSWORD, false );
}
public boolean setFullName(@Nullable String value) {
if (getFullName().equals( value ))
return false;
prefs().edit().putString( PREF_FULL_NAME, value ).apply();
return true;
}
@Nonnull
public String getFullName() {
return prefs().getString( PREF_FULL_NAME, "" );
}
public boolean setDefaultSiteType(@Nonnull MPSiteType value) {
if (getDefaultSiteType().equals( value ))
return false;
prefs().edit().putInt( PREF_SITE_TYPE, value.ordinal() ).apply();
return true;
}
@Nonnull
public MPSiteType getDefaultSiteType() {
return MPSiteType.values()[prefs().getInt( PREF_SITE_TYPE, MPSiteType.GeneratedLong.ordinal() )];
}
public boolean setDefaultVersion(@Nonnull MasterKey.Version value) {
if (getDefaultVersion().equals( value ))
return false;
prefs().edit().putInt( PREF_ALGORITHM_VERSION, value.ordinal() ).apply();
return true;
}
@Nonnull
public MasterKey.Version getDefaultVersion() {
return MasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MasterKey.Version.CURRENT.ordinal() )];
}
}

View File

@@ -3,9 +3,10 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.*;
import android.content.Context;
import android.content.Intent;
import android.os.*;
import android.view.View;
import android.view.WindowManager;
import android.widget.*;
import butterknife.ButterKnife;
import butterknife.InjectView;
@@ -13,7 +14,6 @@ import com.google.common.base.*;
import com.google.common.collect.*;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Set;
import java.util.concurrent.*;
import javax.annotation.Nullable;
@@ -23,6 +23,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( TestActivity.class );
private final Preferences preferences = Preferences.get( this );
private final ListeningExecutorService backgroundExecutor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ListeningExecutorService mainExecutor = MoreExecutors.listeningDecorator( new MainThreadExecutor() );
@@ -38,20 +39,34 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
@InjectView(R.id.actionButton)
Button actionButton;
@InjectView(R.id.nativeKDFField)
CheckBox nativeKDFField;
private MPTestSuite testSuite;
private ListenableFuture<Boolean> testFuture;
private Runnable action;
private ImmutableSet<String> testNames;
public static void startNoSkip(Context context) {
context.startActivity( new Intent( context, TestActivity.class ) );
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
Res.init( getResources() );
getWindow().setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE );
setContentView( R.layout.activity_test );
ButterKnife.inject( this );
nativeKDFField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
preferences.setNativeKDFEnabled( isChecked );
MasterKey.setAllowNativeByDefault( isChecked );
}
} );
try {
setStatus( 0, 0, null );
testSuite = new MPTestSuite();
@@ -76,29 +91,22 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
}
}
@Override
protected void onStart() {
super.onStart();
final Set<String> integrityTestsPassed = getPreferences( MODE_PRIVATE ).getStringSet( "integrityTestsPassed",
ImmutableSet.<String>of() );
if (!FluentIterable.from( testNames ).anyMatch( new Predicate<String>() {
@Override
public boolean apply(@Nullable final String testName) {
return !integrityTestsPassed.contains( testName );
}
} )) {
// None of the tests we need to perform were missing from the tests that have already been passed on this device.
finish();
EmergencyActivity.start( TestActivity.this );
}
}
@Override
protected void onResume() {
super.onResume();
if (testFuture == null) {
nativeKDFField.setChecked( preferences.isAllowNativeKDF() );
if (testFuture == null)
startTestSuite();
}
private void startTestSuite() {
if (testFuture != null)
testFuture.cancel( true );
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
@Override
@@ -107,16 +115,15 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
@Override
public void run() {
getPreferences( MODE_PRIVATE ).edit().putStringSet( "integrityTestsPassed", testNames ).apply();
preferences.setTestsPassed( testNames );
finish();
EmergencyActivity.start( TestActivity.this );
}
} );
else
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
@Override
public void run() {
finish();
startTestSuite();
}
} );
}
@@ -133,7 +140,6 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
}
}, mainExecutor );
}
}
public void onAction(View v) {
if (action != null)

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</parent>
<name>Master Password CLI</name>
@@ -85,7 +85,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</dependency>
<dependency>

View File

@@ -1,15 +1,15 @@
<configuration scan="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
</layout>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
</encoder>
</appender>
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="stdout" />
</root>
</configuration>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</parent>
<name>Master Password GUI</name>
@@ -97,6 +97,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>signing</id>
@@ -134,7 +135,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-model</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->

View File

@@ -1,15 +1,15 @@
<configuration scan="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
</layout>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
</encoder>
</appender>
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="stdout" />
</root>
</configuration>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</parent>
<name>Master Password Site Model</name>
@@ -23,7 +23,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</parent>
<name>Master Password Test Suite</name>
@@ -23,7 +23,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
</dependency>
<!-- TESTING -->

View File

@@ -1,15 +1,15 @@
<configuration scan="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
</layout>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
</encoder>
</appender>
<logger name="com.lyndir.masterpassword" level="${mp.log.level:-TRACE}" />
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="stdout" />
</root>
</configuration>

View File

@@ -15,7 +15,7 @@
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.4-java</version>
<packaging>pom</packaging>
<modules>
@@ -58,4 +58,8 @@
</releases>
</repository>
</repositories>
<scm>
<tag>2.4-java</tag>
</scm>
</project>

View File

@@ -22,6 +22,13 @@
@end
@interface MPSiteQuestionEntity(MP)
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key;
- (void)resolveQuestionAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
@end
@interface MPSiteEntity(MP)<MPFixable>
@property(assign) BOOL loginGenerated;
@@ -38,8 +45,10 @@
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
- (NSString *)resolveSiteAnswerUsingKey:(MPKey *)key;
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
- (void)resolveSiteAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
@end

View File

@@ -35,6 +35,20 @@
@end
@implementation MPSiteQuestionEntity(MP)
- (NSString *)resolveQuestionAnswerUsingKey:(MPKey *)key {
return [self.site.algorithm resolveAnswerForQuestion:self usingKey:key];
}
- (void)resolveQuestionAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.site.algorithm resolveAnswerForQuestion:self usingKey:key result:result];
}
@end
@implementation MPSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
@@ -175,6 +189,11 @@
return [self.algorithm resolvePasswordForSite:self usingKey:key];
}
- (NSString *)resolveSiteAnswerUsingKey:(MPKey *)key {
return [self.algorithm resolveAnswerForSite:self usingKey:key];
}
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolveLoginForSite:self usingKey:key result:result];
@@ -185,6 +204,11 @@
[self.algorithm resolvePasswordForSite:self usingKey:key result:result];
}
- (void)resolveSiteAnswerUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolveAnswerForSite:self usingKey:key result:result];
}
@end
@implementation MPGeneratedSiteEntity(MP)

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="7706"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
</dependencies>
<objects>

View File

@@ -28,6 +28,7 @@
@property(nonatomic) NSString *masterPassword;
@property(nonatomic) BOOL showVersionContainer;
@property(nonatomic) BOOL alternatePressed;
@property(nonatomic) BOOL shiftPressed;
@property(nonatomic) BOOL locked;
@property(nonatomic) BOOL newUser;

View File

@@ -124,6 +124,10 @@
- (void)flagsChanged:(NSEvent *)theEvent {
BOOL shiftPressed = (theEvent.modifierFlags & NSShiftKeyMask) != 0;
if (shiftPressed != self.shiftPressed)
self.shiftPressed = shiftPressed;
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
if (alternatePressed != self.alternatePressed) {
self.alternatePressed = alternatePressed;
@@ -486,7 +490,7 @@
}
// Performing action while content is available. Copy it.
[self copyContent:selectedSite.content];
[self copyContent:self.shiftPressed? selectedSite.answer: selectedSite.content];
[self fadeOut];

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9059"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
<capability name="box content view" minToolsVersion="7.0"/>
<capability name="stacking Non-gravity area distributions on NSStackView" minToolsVersion="7.0" minSystemVersion="10.11"/>
</dependencies>
@@ -28,10 +28,10 @@
<window title="Master Password" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hasShadow="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MPPasswordWindow">
<windowCollectionBehavior key="collectionBehavior" moveToActiveSpace="YES" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="640" height="560"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="640" height="560"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Bwc-sd-6gm" userLabel="Screen Capture">
<rect key="frame" x="-80" y="-80" width="800" height="720"/>
@@ -55,19 +55,16 @@
</configuration>
</ciFilter>
</contentFilters>
<animations/>
<imageCell key="cell" enabled="NO" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="small-screen" id="ArA-2w-I56"/>
</imageView>
<progressIndicator hidden="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="oSh-Ec-8Nf" userLabel="Progress Spinner">
<rect key="frame" x="312" y="524" width="16" height="16"/>
<animations/>
</progressIndicator>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Aue-Zx-6Mf" userLabel="Settings Gear">
<rect key="frame" x="588" y="508" width="32" height="32"/>
<animations/>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="icon_gear" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="i8r-9N-vcQ">
<rect key="frame" x="585" y="496" width="35" height="44"/>
<buttonCell key="cell" type="square" title="⚙" bezelStyle="shadowlessSquare" alignment="center" imageScaling="proportionallyDown" inset="2" id="i8r-9N-vcQ">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<font key="font" size="48" name="AppleSymbols"/>
<string key="keyEquivalent">,</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
@@ -76,12 +73,11 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gAU-xs-aae">
<rect key="frame" x="595" y="490" width="19" height="14"/>
<rect key="frame" x="593" y="478" width="19" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘," id="Xm1-qb-6EP">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -101,7 +97,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" focusRingType="none" alignment="center" usesSingleLineMode="YES" id="NcX-1Z-2OC">
<font key="font" metaFont="system" size="36"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -137,7 +132,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" focusRingType="none" alignment="center" placeholderString="" usesSingleLineMode="YES" id="gRw-5C-YUN">
<font key="font" metaFont="system" size="36"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -173,7 +167,6 @@
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R46-fx-n14">
<rect key="frame" x="232" y="19" width="177" height="19"/>
<animations/>
<buttonCell key="cell" type="recessed" title="Reset My Master Password" bezelStyle="recessed" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="B7Z-72-fVP" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
@@ -193,7 +186,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.70000000000000007" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Hold alt ⌥ to temporarily reveal what you've typed." id="4Ep-xX-Ky8">
<font key="font" size="11" name="HelveticaNeue"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -225,7 +217,6 @@
</configuration>
</ciFilter>
</contentFilters>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="KeljXoleKowi9@" placeholderString="" id="WVV-EE-tkB">
<font key="font" size="64" name="SourceCodePro-Regular"/>
<color key="textColor" name="keyboardFocusIndicatorColor" catalog="System" colorSpace="catalog"/>
@@ -240,19 +231,77 @@
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.displayedContent" id="Sdg-fb-kQK"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ond-dT-x5d" userLabel="Site Password Tip">
<rect key="frame" x="220" y="146" width="160" height="14"/>
<searchField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eBx-2g-chS" userLabel="Site Question">
<rect key="frame" x="42" y="33" width="516" height="14"/>
<constraints>
<constraint firstAttribute="width" constant="512" id="JSe-85-4H0"/>
</constraints>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="calibratedRGB"/>
</shadow>
<searchFieldCell key="cell" controlSize="small" selectable="YES" editable="YES" focusRingType="none" alignment="center" placeholderString="Question Keyword" sendsSearchStringImmediately="YES" id="sZN-Vi-v8k">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="doSearchSites:" target="-2" id="72O-59-xYV"/>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="Skr-1g-quT">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.question" id="3AM-pr-HBA"/>
<outlet property="delegate" destination="-2" id="c5n-ve-3Uw"/>
</connections>
</searchField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cp4-2G-8aG" userLabel="Site Answer">
<rect key="frame" x="189" y="12" width="221" height="29"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<contentFilters>
<ciFilter name="CIGloom">
<configuration>
<null key="inputImage"/>
<real key="inputIntensity" value="0.80000000000000004"/>
<real key="inputRadius" value="8"/>
</configuration>
</ciFilter>
</contentFilters>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="fewc fom pugsuhe xav" placeholderString="" id="oYj-IR-xmw">
<font key="font" size="18" name="SourceCodePro-Regular"/>
<color key="textColor" name="keyboardFocusIndicatorColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="TxZ-TU-RMn">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.answer" id="Ba7-t3-euS"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ond-dT-x5d" userLabel="Site Password Tip">
<rect key="frame" x="220" y="138" width="160" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Your password for apple.com:" id="CgJ-XZ-6Hy">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="Jvv-jZ-PG1">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="hidden2" keyPath="selection.content.length" previousBinding="Jvv-jZ-PG1" id="OXN-Zm-1ra">
<dictionary key="options">
<integer key="NSMultipleValuesPlaceholder" value="-1"/>
@@ -262,11 +311,6 @@
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="Jvv-jZ-PG1">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">
@@ -275,7 +319,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No password set. Click &quot;Change Password&quot; on the bottom to set one." id="eDQ-iz-97a">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -312,7 +355,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Master Password generates passwords for your sites (and other things)." id="YyD-hd-wi3">
<font key="font" size="16" name="HelveticaNeue"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -341,7 +383,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" id="9c4-NI-NM0">
<font key="font" size="12" name="HelveticaNeue"/>
<string key="title">— When you create a new account, use the password Master Password gives you for it.
@@ -373,16 +414,19 @@
<constraints>
<constraint firstAttribute="centerY" secondItem="XUV-zU-Y9c" secondAttribute="centerY" id="6Qf-5O-Cvk"/>
<constraint firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="7sl-qi-HY9"/>
<constraint firstItem="cp4-2G-8aG" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="Dx0-IB-hUM"/>
<constraint firstItem="Ia6-7b-dFr" firstAttribute="centerY" secondItem="XUV-zU-Y9c" secondAttribute="centerY" id="KqM-uR-Obm"/>
<constraint firstItem="Ia6-7b-dFr" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="NFQ-aw-8tm"/>
<constraint firstAttribute="centerX" secondItem="sYt-eL-uwt" secondAttribute="centerX" id="OFw-vb-I71"/>
<constraint firstItem="XUV-zU-Y9c" firstAttribute="top" secondItem="Ond-dT-x5d" secondAttribute="bottom" constant="8" symbolic="YES" id="UgV-J6-B5T"/>
<constraint firstItem="cp4-2G-8aG" firstAttribute="top" secondItem="XUV-zU-Y9c" secondAttribute="bottom" id="T6W-P9-0vj"/>
<constraint firstItem="XUV-zU-Y9c" firstAttribute="top" secondItem="Ond-dT-x5d" secondAttribute="bottom" id="UgV-J6-B5T"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="UhT-LQ-aZ8"/>
<constraint firstItem="eBx-2g-chS" firstAttribute="centerX" secondItem="cp4-2G-8aG" secondAttribute="centerX" id="cHz-Q1-8ii"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="top" secondItem="OaQ-of-zmb" secondAttribute="bottom" constant="8" symbolic="YES" id="hjJ-f1-mFv"/>
<constraint firstItem="cp4-2G-8aG" firstAttribute="top" secondItem="eBx-2g-chS" secondAttribute="bottom" constant="-8" id="inf-AC-ger"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="centerX" secondItem="OaQ-of-zmb" secondAttribute="centerX" id="mu2-se-Mtn"/>
<constraint firstAttribute="centerY" secondItem="sYt-eL-uwt" secondAttribute="centerY" id="zLS-QG-MKS"/>
</constraints>
<animations/>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OnR-s6-d4P" userLabel="Site Name Label">
<rect key="frame" x="209" y="310" width="223" height="20"/>
@@ -390,7 +434,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="calibratedRGB"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Maarten Billemont's password for:" id="1Lb-d0-fQD">
<font key="font" size="14" name="HelveticaNeue"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -406,7 +449,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="calibratedRGB"/>
</shadow>
<animations/>
<searchFieldCell key="cell" selectable="YES" editable="YES" focusRingType="none" alignment="center" placeholderString="Site Name" sendsSearchStringImmediately="YES" id="ppl-2c-1E9">
<font key="font" size="36" name="HelveticaNeue-Thin"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -424,7 +466,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.69999999999999996" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Type the name of your site (eg. apple.com), then hit enter ⏎ to create a password for it." id="QTI-cz-Onx">
<font key="font" size="11" name="HelveticaNeue"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -443,14 +484,15 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rhm-sC-xFS" userLabel="Site Name Tip">
<rect key="frame" x="37" y="235" width="566" height="15"/>
<rect key="frame" x="150" y="220" width="340" height="30"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.80000000000000004" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Hit enter ⏎ to copy the password, then paste it using ⌘V. Use the arrows ⇅ to navigate the list or esc ⎋ to exit." id="n3W-XU-dya">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="n3W-XU-dya">
<font key="font" size="11" name="HelveticaNeue"/>
<string key="title">Hit enter ⏎ to copy the password, hold shift ⇧ to copy the answer.
Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -468,15 +510,14 @@
</connections>
</textField>
<scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="35" horizontalPageScroll="10" verticalLineScroll="35" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Bme-XK-MMc" userLabel="Sites Table">
<rect key="frame" x="64" y="80" width="512" height="147"/>
<rect key="frame" x="64" y="80" width="512" height="132"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="e11-59-xSS">
<rect key="frame" x="0.0" y="0.0" width="512" height="147"/>
<rect key="frame" x="0.0" y="0.0" width="512" height="132"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPSitesTableView">
<rect key="frame" x="0.0" y="0.0" width="515" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="515" height="132"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" white="1" alpha="0.0" colorSpace="deviceWhite"/>
<color key="gridColor" name="selectedControlColor" catalog="System" colorSpace="catalog"/>
@@ -504,7 +545,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="0.80000000000000004" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4">
<font key="font" size="24" name="HelveticaNeue-Thin"/>
<color key="textColor" name="alternateSelectedControlTextColor" catalog="System" colorSpace="catalog"/>
@@ -533,7 +573,6 @@
</configuration>
</ciFilter>
</backgroundFilters>
<animations/>
<connections>
<outlet property="textField" destination="ydd-Rv-tra" id="lMV-D4-Ilq"/>
</connections>
@@ -549,21 +588,17 @@
</connections>
</tableView>
</subviews>
<animations/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="calibratedRGB"/>
</clipView>
<constraints>
<constraint firstAttribute="width" constant="512" id="qfu-pO-SvM"/>
</constraints>
<animations/>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="8wr-pu-1lc">
<rect key="frame" x="-100" y="-100" width="512" height="15"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="mcf-ST-XXI">
<autoresizingMask key="autoresizingMask"/>
<animations/>
</scroller>
<connections>
<binding destination="-2" name="hidden" keyPath="locked" id="FF1-c9-zmm"/>
@@ -571,7 +606,6 @@
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="brI-fg-Kav">
<rect key="frame" x="260" y="45" width="122" height="19"/>
<animations/>
<buttonCell key="cell" type="recessed" title="Change Password" bezelStyle="recessed" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="FQu-fM-NWY" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
@@ -602,7 +636,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘P" id="MyN-x6-dMk">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -640,7 +673,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Click to set a password:" id="gjc-Fw-xa1">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -678,7 +710,6 @@
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vES-W5-m4x">
<rect key="frame" x="243" y="19" width="155" height="19"/>
<animations/>
<buttonCell key="cell" type="recessed" title="Change Password Type" bezelStyle="recessed" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Fom-sN-EtZ" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
@@ -700,7 +731,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘T" id="HFM-Bk-akx">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -725,7 +755,6 @@
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XuF-Sp-6JD">
<rect key="frame" x="406" y="19" width="80" height="19"/>
<animations/>
<buttonCell key="cell" type="recessed" title="Delete Site" bezelStyle="recessed" alignment="center" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="26m-of-YMQ" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
@@ -747,7 +776,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘D" id="PPC-be-w4E">
<font key="font" size="11" name="HelveticaNeue"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -772,7 +800,6 @@
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1Qo-iG-CQt">
<rect key="frame" x="126" y="19" width="109" height="19"/>
<animations/>
<buttonCell key="cell" type="recessed" title="Set Login Name" bezelStyle="recessed" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="QFo-9y-aVe" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
@@ -799,7 +826,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘L" id="fUB-rF-7x8">
<font key="font" size="11" name="HelveticaNeue"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -828,7 +854,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Upgrade your site's algorithm version for maximum protection:" id="3ds-qG-YNd">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -856,7 +881,6 @@
<subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mcq-qD-yte">
<rect key="frame" x="-3" y="-3" width="19" height="27"/>
<animations/>
<stepperCell key="cell" continuous="YES" alignment="left" minValue="1" maxValue="1000" doubleValue="1" id="73y-03-zHt"/>
<connections>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.algorithmVersion" id="GyA-hK-6cD"/>
@@ -868,7 +892,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="V1" id="Pjy-Fm-zwB">
<font key="font" size="12" name="HelveticaNeue-Medium"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -879,7 +902,6 @@
</connections>
</textField>
</subviews>
<animations/>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
@@ -901,7 +923,6 @@
<subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XgA-Vl-CKh" userLabel="Counter Stepper">
<rect key="frame" x="-3" y="-3" width="19" height="27"/>
<animations/>
<stepperCell key="cell" continuous="YES" alignment="left" minValue="1" maxValue="1000" doubleValue="1" id="ikF-n4-xiI"/>
<connections>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.counter" id="qmm-6z-boy"/>
@@ -913,7 +934,6 @@
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</shadow>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="1" id="dhQ-bJ-rn3">
<font key="font" size="12" name="HelveticaNeue-Medium"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
@@ -924,7 +944,6 @@
</connections>
</textField>
</subviews>
<animations/>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
@@ -934,6 +953,11 @@
<real value="3.4028234663852886e+38"/>
</customSpacing>
<connections>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="9vk-TH-Bm2">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="hidden2" keyPath="selection.generated" previousBinding="9vk-TH-Bm2" id="B6u-H8-b9o">
<dictionary key="options">
<integer key="NSMultipleValuesPlaceholder" value="-1"/>
@@ -943,11 +967,6 @@
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="9vk-TH-Bm2">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</stackView>
</subviews>
@@ -1014,7 +1033,6 @@
<constraint firstItem="gAU-xs-aae" firstAttribute="centerX" secondItem="Aue-Zx-6Mf" secondAttribute="centerX" id="yxU-bl-dmQ"/>
<constraint firstItem="npC-Kk-gUM" firstAttribute="top" secondItem="CnS-iI-dhr" secondAttribute="bottom" constant="8" symbolic="YES" id="zs0-eA-5MW"/>
</constraints>
<animations/>
</view>
<point key="canvasLocation" x="-267" y="279"/>
</window>
@@ -1024,16 +1042,15 @@
<binding destination="-2" name="contentArray" keyPath="sites" id="c96-Dv-HK1"/>
</connections>
</arrayController>
<box autoresizesSubviews="NO" misplaced="YES" title="Password Types" borderType="line" titlePosition="noTitle" id="bZe-7q-i6q">
<box autoresizesSubviews="NO" title="Password Types" borderType="line" titlePosition="noTitle" id="bZe-7q-i6q">
<rect key="frame" x="0.0" y="0.0" width="416" height="250"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" id="hAc-y9-IMT">
<rect key="frame" x="1" y="1" width="414" height="248"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<matrix verticalHuggingPriority="750" misplaced="YES" allowsEmptySelection="NO" autorecalculatesCellSize="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3fr-Fd-pxx">
<matrix verticalHuggingPriority="750" allowsEmptySelection="NO" autorecalculatesCellSize="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3fr-Fd-pxx">
<rect key="frame" x="18" y="78" width="378" height="158"/>
<animations/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="179" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
@@ -1078,9 +1095,8 @@
</column>
</cells>
</matrix>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kCO-1M-Wz1">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kCO-1M-Wz1">
<rect key="frame" x="16" y="14" width="382" height="56"/>
<animations/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="kl0-P1-6ZY">
<font key="font" metaFont="toolTip"/>
<string key="title">"Personal password" allows you to store your own password. It cannot be regenerated in the event of loss. "Device private password" is similar but will never leave your device: it cannot be synced, backed up or exported.</string>
@@ -1089,7 +1105,6 @@
</textFieldCell>
</textField>
</subviews>
<animations/>
</view>
<constraints>
<constraint firstItem="kCO-1M-Wz1" firstAttribute="top" secondItem="3fr-Fd-pxx" secondAttribute="bottom" constant="8" symbolic="YES" id="GSx-ci-oqR"/>
@@ -1100,13 +1115,12 @@
<constraint firstItem="3fr-Fd-pxx" firstAttribute="leading" secondItem="bZe-7q-i6q" secondAttribute="leading" constant="16" id="w0i-lL-lPE"/>
<constraint firstItem="kCO-1M-Wz1" firstAttribute="leading" secondItem="bZe-7q-i6q" secondAttribute="leading" constant="16" id="wBb-UE-BfN"/>
</constraints>
<animations/>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="message" size="11"/>
</box>
</objects>
<resources>
<image name="icon_gear" width="32" height="32"/>
<image name="small-screen" width="25" height="15.5"/>
</resources>
</document>

View File

@@ -31,6 +31,8 @@
@property (nonatomic) NSString *typeName;
@property (nonatomic) NSString *content;
@property (nonatomic) NSString *displayedContent;
@property (nonatomic) NSString *question;
@property (nonatomic) NSString *answer;
@property (nonatomic) NSString *loginName;
@property (nonatomic) NSNumber *uses;
@property (nonatomic) NSUInteger counter;

View File

@@ -194,13 +194,18 @@
[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];
[self updatePasswordWithResult:
[self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter
usingKey:[MPAppDelegate_Shared get].key]];
[self updateLoginNameWithResult:
[self.algorithm generateLoginForSiteNamed:self.name
usingKey:[MPAppDelegate_Shared get].key]];
[self updateAnswerWithResult:
[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question
usingKey:[MPAppDelegate_Shared get].key]];
} );
}
@@ -212,6 +217,9 @@
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
[self updateLoginNameWithResult:result];
}];
[entity resolveSiteAnswerUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
[self updateAnswerWithResult:result];
}];
}
- (void)updatePasswordWithResult:(NSString *)result {
@@ -239,4 +247,11 @@
} );
}
- (void)updateAnswerWithResult:(NSString *)answer {
PearlMainQueue( ^{
self.answer = answer;
} );
}
@end

View File

@@ -178,7 +178,7 @@
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
NSString *body;
if (!_multiple) {
NSObject *answer = [site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key];
NSObject *answer = [site resolveSiteAnswerUsingKey:[MPiOSAppDelegate get].key];
body = strf( @"Master Password generated the following security answer for your site: %@\n\n"
@"%@\n"
@"\n\nYou should use this as the answer to each security question the site asks you.\n"
@@ -188,7 +188,7 @@
NSMutableString *bodyBuilder = [NSMutableString string];
[bodyBuilder appendFormat:@"Master Password generated the following security answers for your site: %@\n\n", site.name];
for (MPSiteQuestionEntity *question in site.questions) {
NSObject *answer = [site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key];
NSObject *answer = [question resolveQuestionAnswerUsingKey:[MPiOSAppDelegate get].key];
[bodyBuilder appendFormat:@"For question: '%@', use answer: %@\n", question.keyword, answer];
}
[bodyBuilder appendFormat:@"\n\nUse the answer for the matching security question.\n"
@@ -241,7 +241,7 @@
self.titleLabel.text = strl( @"Answer for %@:", site.name );
self.answerField.text = @"...";
[site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
[site resolveSiteAnswerUsingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
PearlMainQueue( ^{
self.answerField.text = result;
} );
@@ -330,7 +330,7 @@
PearlMainQueue( ^{
self.answerField.text = @"...";
} );
[site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
[question resolveQuestionAnswerUsingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
PearlMainQueue( ^{
self.questionField.text = keyword;
self.answerField.text = result;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 764 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB