2
0

Better abstraction for key & algorithm + V1

[IMPROVED]  A master password key is now better abstracted in an object.
[IMPROVED]  A master password algorithm is now better astracted in an
            object.
[ADDED]     Elements now have a specific algorithm version.
[ADDED]     Automatic/explicit migration of elements.
[ADDED]     Searching outdated elements.
This commit is contained in:
Maarten Billemont 2012-07-17 22:57:11 +02:00
parent 5ca0d954bb
commit 7be9884075
33 changed files with 933 additions and 283 deletions

View File

@ -2,7 +2,9 @@
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="FunctionImplicitDeclarationInspection" 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="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 032b71a820d7f9526a42f51a00023e8247cd0943
Subproject commit 36320eaa305fce113b6c12e45c99d9a61dee80b6

View File

@ -7,13 +7,20 @@
objects = {
/* Begin PBXBuildFile section */
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E81EFABC6085AC8AE69 /* MPKey.m */; };
93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */; };
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */; };
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; };
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
DA0757EA15B3681200613FAA /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0757E915B3681200613FAA /* MPElementEntity.m */; };
DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3215B55397007ED9BC /* MPUserEntity.m */; };
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */; };
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */; };
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */; };
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
@ -101,7 +108,6 @@
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
@ -673,9 +679,6 @@
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B515036BF600CED3BC /* lock_red@2x.png */; };
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B615036BF600CED3BC /* logo-bare.png */; };
DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */; };
DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */; };
DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
@ -887,9 +890,15 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = "<group>"; };
93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = "<group>"; };
93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = "<group>"; };
93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = "<group>"; };
93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV1.h; sourceTree = "<group>"; };
93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = "<group>"; };
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
DA0757E815B3681200613FAA /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DA0757E915B3681200613FAA /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DA0A1D0315690A9A0092735D /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = Resources/Default.png; sourceTree = SOURCE_ROOT; };
DA0A1D0415690A9A0092735D /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "Resources/Default@2x.png"; sourceTree = SOURCE_ROOT; };
DA0A1D0715690AD40092735D /* tip_arrow_banana.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tip_arrow_banana.png; path = Resources/Tooltips/tip_arrow_banana.png; sourceTree = SOURCE_ROOT; };
@ -902,6 +911,14 @@
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
@ -1003,7 +1020,6 @@
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
@ -1642,12 +1658,6 @@
DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_teal@2x.png"; sourceTree = "<group>"; };
DAB8D6F815036BF600CED3BC /* tip_location_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_location_wood.png; sourceTree = "<group>"; };
DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_wood@2x.png"; sourceTree = "<group>"; };
DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DABB980C150FF40100B05417 /* SendToMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SendToMac-Prefix.pch"; sourceTree = "<group>"; };
DABB980D150FF40100B05417 /* SendToMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendToMac.h; sourceTree = "<group>"; };
DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = "<group>"; };
@ -1999,17 +2009,25 @@
DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
isa = PBXGroup;
children = (
DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */,
DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */,
DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */,
DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */,
DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */,
DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */,
93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */,
93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */,
93D39B0DF5E3C56355186738 /* MPAlgorithm.m */,
93D39C68AFA48A13015E4FAC /* MPKey.h */,
93D39E81EFABC6085AC8AE69 /* MPKey.m */,
93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */,
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */,
93D398E394E311C545E0A057 /* MPAlgorithm.h */,
DA0E07941577FE490008A67E /* MPEntities.h */,
DA0E07951577FE490008A67E /* MPEntities.m */,
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DA0757E815B3681200613FAA /* MPElementEntity.h */,
DA0757E915B3681200613FAA /* MPElementEntity.m */,
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */,
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */,
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */,
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */,
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */,
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */,
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */,
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */,
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@ -2019,7 +2037,6 @@
DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
DAB8D43E15036BCF00CED3BC /* iOS */,
);
path = MasterPassword;
@ -4238,17 +4255,20 @@
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */,
DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */,
DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */,
DA0757EA15B3681200613FAA /* MPElementEntity.m in Sources */,
93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */,
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */,
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */,
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */,
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */,
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */,
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */,
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,44 @@
/**
* 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
*/
//
// MPAlgorithm
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPKey.h"
#import "MPElementGeneratedEntity.h"
#define MPAlgorithmDefaultVersion 1
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
@protocol MPAlgorithm<NSObject>
@required
- (NSUInteger)version;
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
- (MPKey *)keyFromKeyData:(NSData *)keyData;
- (NSData *)keyIDForKeyData:(NSData *)keyData;
- (NSString *)nameOfType:(MPElementType)type;
- (NSString *)shortNameOfType:(MPElementType)type;
- (NSString *)classNameOfType:(MPElementType)type;
- (Class)classOfType:(MPElementType)type;
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
@end
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);

View File

@ -0,0 +1,42 @@
/**
* 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
*/
//
// MPAlgorithm
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
#import "MPEntities.h"
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
static NSMutableDictionary *versionToAlgorithm = nil;
if (!versionToAlgorithm)
versionToAlgorithm = [NSMutableDictionary dictionary];
id<MPAlgorithm> algorithm = [versionToAlgorithm objectForKey:PearlUnsignedInteger(version)];
if (!algorithm)
if ((algorithm = [NSClassFromString(PearlString(@"MPAlgorithmV%u", version)) new]))
[versionToAlgorithm setObject:algorithm forKey:PearlUnsignedInteger(version)];
return algorithm;
}
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
if (PearlCFBundleVersionCompare(bundleVersion, @"1.3") == NSOrderedAscending)
// Pre-1.3
return MPAlgorithmForVersion(0);
return MPAlgorithmDefault;
}

View File

@ -0,0 +1,21 @@
/**
* 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
*/
//
// MPAlgorithmV0
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
@interface MPAlgorithmV0 : NSObject <MPAlgorithm>
@end

View File

@ -1,54 +1,83 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPTypes.m
// MasterPassword
// MPAlgorithmV0
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV0.h"
#import "MPEntities.h"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_dkLen 64
#define MP_hash PearlHashSHA256
NSData *keyForPassword(NSString *password, NSString *username) {
@implementation MPAlgorithmV0
uint32_t nusernameLength = htonl(username.length);
- (NSUInteger)version {
return 0;
}
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
if (element.version != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
// This migration requires explicit permission.
element.requiresExplicitMigration = YES;
return NO;
}
// Apply migration.
element.requiresExplicitMigration = NO;
element.version = [self version];
return YES;
}
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
uint32_t nuserNameLength = htonl(userName.length);
NSDate *start = [NSDate date];
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
NSData *keyData = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nusernameLength
length:sizeof(nusernameLength)],
[username dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nuserNameLength
length:sizeof(nuserNameLength)],
[userName dataUsingEncoding:NSUTF8StringEncoding],
nil] N:MP_N r:MP_r p:MP_p];
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
MPKey *key = [self keyFromKeyData:keyData];
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
return key;
}
- (MPKey *)keyFromKeyData:(NSData *)keyData {
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
}
- (NSData *)keyIDForKeyData:(NSData *)keyData {
NSData *keyIDForPassword(NSString *password, NSString *username) {
return keyIDForKey(keyForPassword(password, username));
return [keyData hashWith:MP_hash];
}
NSData *keyIDForKey(NSData *key) {
return [key hashWith:MP_hash];
}
NSString *NSStringFromMPElementType(MPElementType type) {
- (NSString *)nameOfType:(MPElementType)type {
if (!type)
return nil;
@ -77,13 +106,12 @@ NSString *NSStringFromMPElementType(MPElementType type) {
case MPElementTypeStoredDevicePrivate:
return @"Device Private Password";
default:
Throw(@"Type not supported: %d", type);
}
Throw(@"Type not supported: %d", type);
}
NSString *NSStringShortFromMPElementType(MPElementType type) {
- (NSString *)shortNameOfType:(MPElementType)type {
if (!type)
return nil;
@ -112,13 +140,17 @@ NSString *NSStringShortFromMPElementType(MPElementType type) {
case MPElementTypeStoredDevicePrivate:
return @"Device";
default:
Throw(@"Type not supported: %d", type);
}
Throw(@"Type not supported: %d", type);
}
Class ClassFromMPElementType(MPElementType type) {
- (NSString *)classNameOfType:(MPElementType)type {
return NSStringFromClass([self classOfType:type]);
}
- (Class)classOfType:(MPElementType)type {
if (!type)
return nil;
@ -147,63 +179,56 @@ Class ClassFromMPElementType(MPElementType type) {
case MPElementTypeStoredDevicePrivate:
return [MPElementStoredEntity class];
default:
Throw(@"Type not supported: %d", type);
}
Throw(@"Type not supported: %d", type);
}
NSString *ClassNameFromMPElementType(MPElementType type) {
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
return NSStringFromClass(ClassFromMPElementType(type));
}
static NSDictionary *MPTypes_ciphers = nil;
static NSDictionary *MPTypes_ciphers = nil;
if (!element)
return nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
if (!(element.type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", [self nameOfType:element.type], element.name);
return nil;
}
if (!name.length) {
if (!element.name.length) {
err(@"Missing name.");
return nil;
}
if (!key.length) {
if (!key.keyData.length) {
err(@"Missing key.");
return nil;
}
if (!counter)
// Counter unset, go into OTP mode.
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key encodeBase64], [nameLengthBytes encodeHex], name, [counterBytes encodeHex]);
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], element.name, [counterBytes encodeHex]);
NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
nameLengthBytes,
[name dataUsingEncoding:NSUTF8StringEncoding],
[element.name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes,
nil]
hmacWith:PearlHashSHA256 key:key];
hmacWith:PearlHashSHA256 key:key.keyData];
trc(@"seed is: %@", [seed encodeBase64]);
const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte.
assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)];
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
valueForKey:[self nameOfType:element.type]];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:element.type], typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the cipher.
assert([seed length] >= [cipher length] + 1);
@ -222,10 +247,4 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
return content;
}
void MPElementMigrate(MPElementEntity *element, BOOL explicit) {
if (element.version == 0 && explicit) {
// 0 -> 1
element.version = 1;
}
}
@end

View File

@ -0,0 +1,22 @@
/**
* 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
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV0.h"
@interface MPAlgorithmV1 : MPAlgorithmV0
@end

View File

@ -0,0 +1,113 @@
/**
* 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
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV1.h"
#import "MPEntities.h"
@implementation MPAlgorithmV1
- (NSUInteger)version {
return 1;
}
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
if (element.version != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (element.type & MPElementTypeClassGenerated) {
// This migration requires explicit permission for types of the generated class.
element.requiresExplicitMigration = YES;
return NO;
}
}
// Apply migration.
element.requiresExplicitMigration = NO;
element.version = [self version];
return YES;
}
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
static NSDictionary *MPTypes_ciphers = nil;
if (!element)
return nil;
if (!(element.type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", [self nameOfType:element.type], element.name);
return nil;
}
if (!element.name.length) {
err(@"Missing name.");
return nil;
}
if (!key.keyData.length) {
err(@"Missing key.");
return nil;
}
if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password
uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], element.name, [counterBytes encodeHex]);
NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
nameLengthBytes,
[element.name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes,
nil]
hmacWith:PearlHashSHA256 key:key.keyData];
trc(@"seed is: %@", [seed encodeBase64]);
const unsigned char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte.
assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
valueForKey:[self nameOfType:element.type]];
NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:element.type], typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the cipher.
assert([seed length] >= [cipher length] + 1);
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = seedBytes[c + 1];
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
1)];
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
[content appendString:character];
}
return content;
}
@end

View File

@ -23,7 +23,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
matches:nil];
}
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (key)
@ -34,7 +34,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
inf(@"No key found in keychain for: %@", user.userID);
}
return key;
return [MPAlgorithmDefault keyFromKeyData:key];
}
- (void)storeSavedKeyFor:(MPUserEntity *)user {
@ -42,14 +42,14 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (user.saveKey) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (![existingKey isEqualToData:self.key]) {
if (![existingKey isEqualToData:self.key.keyData]) {
inf(@"Saving key in keychain for: %@", user.userID);
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
@ -87,13 +87,13 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
NSData *tryKey = nil;
MPKey *tryKey = nil;
// Method 1: When the user has no keyID set, set a new key from the given master password.
if (!user.keyID) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name))) {
user.keyID = keyIDForKey(tryKey);
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
user.keyID = tryKey.keyID;
[[MPAppDelegate_Shared get] saveContext];
}
}
@ -107,7 +107,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (!tryKey) {
// Key should be saved in keychain. Load it.
if ((tryKey = [self loadSavedKeyFor:user]))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
if (![user.keyID isEqual:tryKey.keyID]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
inf(@"Saved password doesn't match keyID for: %@", user.userID);
@ -119,8 +119,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
// Method 3: Check the given master password string.
if (!tryKey) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name)))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name]))
if (![user.keyID isEqual:tryKey.keyID]) {
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
tryKey = nil;
@ -142,7 +142,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
}
inf(@"Logged in: %@", user.userID);
if (![self.key isEqualToData:tryKey]) {
if (![self.key isEqualToKey:tryKey]) {
self.key = tryKey;
[self storeSavedKeyFor:user];
}
@ -160,6 +160,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
user.lastUsed = [NSDate date];
self.activeUser = user;
self.activeUser.requiresExplicitMigration = NO;
[[MPAppDelegate_Shared get] saveContext];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];

View File

@ -16,7 +16,7 @@
#endif
@property (strong, nonatomic) MPUserEntity *activeUser;
@property (strong, nonatomic) NSData *key;
@property (strong, nonatomic) MPKey *key;
+ (MPAppDelegate_Shared *)get;

View File

@ -142,7 +142,8 @@
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO"
forKey:@"enabled"]];
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
}
@ -168,13 +169,11 @@
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
attributes:nil];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible attributes:nil];
manager.hardResetEnabled = YES;
[manager hardResetLocalStorage];
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
return;
Throw(@"Local store was reset, application must be restarted to use it.");
}
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
wrn(@"iCloud store could not be opened, resetting it.");
@ -182,8 +181,7 @@
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
attributes:nil];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible attributes:nil];
manager.hardResetEnabled = YES;
[manager hardResetCloudStorage];
break;
@ -209,7 +207,7 @@
}
if (!sitePattern) {
sitePattern = [[NSRegularExpression alloc]
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)"
options:0 error:&error];
if (error)
err(@"Error loading the site pattern: %@", error);
@ -217,9 +215,9 @@
if (!headerPattern || !sitePattern)
return MPImportResultInternalError;
NSData *key = nil;
NSString *keyIDHex = nil, *userName = nil;
MPKey *key = nil;
MPUserEntity *user = nil;
NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
@ -251,16 +249,15 @@
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
userName = headerValue;
key = keyForPassword(password, userName);
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
}
if ([headerName isEqualToString:@"Key ID"]) {
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
return MPImportResultInvalidPassword;
}
if ([headerName isEqualToString:@"Key ID"])
keyIDHex = headerValue;
if ([headerName isEqualToString:@"Version"])
bundleVersion = headerValue;
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
@ -272,6 +269,9 @@
continue;
if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput;
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
return MPImportResultInvalidPassword;
if (![importedSiteLine length])
continue;
@ -285,8 +285,9 @@
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
// Find existing site.
if (user) {
@ -300,7 +301,7 @@
}
[elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, version, name, exportContent, nil]];
}
}
@ -331,17 +332,19 @@
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSString *name = [siteElements objectAtIndex:3];
NSString *exportContent = [siteElements objectAtIndex:4];
NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue];
NSString *name = [siteElements objectAtIndex:4];
NSString *exportContent = [siteElements objectAtIndex:5];
// Create new site.
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
inManagedObjectContext:self.managedObjectContext];
element.name = name;
element.user = user;
element.type = type;
element.uses = uses;
element.lastUsed = lastUsed;
element.version = version;
if ([exportContent length]) {
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
@ -355,8 +358,7 @@
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
attributes:nil];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil];
return MPImportResultSuccess;
}
@ -405,7 +407,8 @@
}
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type,
[name cStringUsingEncoding:NSUTF8StringEncoding], content
? content: @""];
}

View File

@ -2,7 +2,7 @@
// MPElementEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 15/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -18,9 +18,9 @@
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSString * userName;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSString * userName;
@property (nonatomic, retain) MPUserEntity *user;
@end

View File

@ -2,7 +2,7 @@
// MPElementEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 15/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -17,9 +17,9 @@
@dynamic name;
@dynamic requiresExplicitMigration_;
@dynamic type_;
@dynamic userName;
@dynamic uses_;
@dynamic version_;
@dynamic userName;
@dynamic user;
@end

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 10/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 10/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 10/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 10/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -11,20 +11,27 @@
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h"
#import "MPUserEntity.h"
#import "MPAlgorithm.h"
#define MPAvatarCount 19
@interface MPElementEntity (MP)
@property (assign) MPElementType type;
@property (readonly) NSString *typeName;
@property (readonly) NSString *typeShortName;
@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;
- (NSUInteger)use;
- (NSString *)exportContent;
- (void)importProtectedContent:(NSString *)protectedContent;
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
- (BOOL)migrateExplicitly:(BOOL)explicit;
@end
@ -39,6 +46,7 @@
@property (assign) NSUInteger avatar;
@property (assign) BOOL saveKey;
@property (assign) MPElementType defaultType;
@property (assign) BOOL requiresExplicitMigration;
@property (readonly) NSString *userID;
+ (NSString *)idFor:(NSString *)userName;

View File

@ -8,8 +8,6 @@
#import "MPEntities.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPUserEntity.h"
@implementation MPElementEntity (MP)
@ -23,6 +21,26 @@
self.type_ = PearlUnsignedInteger(aType);
}
- (NSString *)typeName {
return [self.algorithm nameOfType:self.type];
}
- (NSString *)typeShortName {
return [self.algorithm shortNameOfType:self.type];
}
- (NSString *)typeClassName {
return [self.algorithm classNameOfType:self.type];
}
- (Class)typeClass {
return [self.algorithm classOfType:self.type];
}
- (NSUInteger)uses {
return [self.uses_ unsignedIntegerValue];
@ -53,6 +71,11 @@
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
}
- (id<MPAlgorithm>)algorithm {
return MPAlgorithmForVersion(self.version);
}
- (NSUInteger)use {
self.lastUsed = [NSDate date];
@ -73,7 +96,7 @@
}
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
}
@ -84,8 +107,22 @@
- (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed);
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@, version=%d, userName=%@, requiresExplicitMigration=%d}",
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed, self.version,
self.userName, self.requiresExplicitMigration);
}
- (BOOL)migrateExplicitly:(BOOL)explicit {
while (self.version < MPAlgorithmDefaultVersion)
if ([MPAlgorithmForVersion(self.version + 1) migrateElement:self explicit:explicit])
inf(@"%@ migration to version: %d succeeded for element: %@", explicit? @"Explicit": @"Automatic", self.version + 1, self);
else {
wrn(@"%@ migration to version: %d failed for element: %@", explicit? @"Explicit": @"Automatic", self.version + 1, self);
return NO;
}
return YES;
}
@end
@ -104,7 +141,7 @@
- (id)content {
NSData *key = [MPAppDelegate get].key;
MPKey *key = [MPAppDelegate get].key;
if (!key)
return nil;
@ -116,7 +153,7 @@
if (![self.name length])
return nil;
return MPCalculateContent(self.type, self.name, key, self.counter);
return [self.algorithm generateContentForElement:self usingKey:key];
}
@end
@ -135,7 +172,7 @@
- (id)content {
NSData *key = [MPAppDelegate get].key;
MPKey *key = [MPAppDelegate get].key;
if (!key)
return nil;
@ -144,17 +181,17 @@
- (void)setContent:(id)content {
NSData *key = [MPAppDelegate get].key;
MPKey *key = [MPAppDelegate get].key;
if (!key)
return;
[self setContent:content usingKey:key];
}
- (id)contentUsingKey:(NSData *)key {
- (id)contentUsingKey:(MPKey *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
assert([key.keyID isEqualToData:self.user.keyID]);
NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate)
@ -162,23 +199,27 @@
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
NSData *decryptedContent = nil;
if ([encryptedContent length])
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content usingKey:(NSData *)key {
- (void)setContent:(id)content usingKey:(MPKey *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
assert([key.keyID isEqualToData:self.user.keyID]);
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
(__bridge id)kSecAttrAccessible,
#endif
nil]];
self.contentObject = nil;
@ -196,7 +237,7 @@
self.contentObject = [protectedContent decodeBase64];
}
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
[self setContent:clearContent usingKey:key];
}
@ -230,17 +271,26 @@
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
- (void)setDefaultType:(MPElementType)aDefaultType {
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
}
- (BOOL)requiresExplicitMigration {
return [self.requiresExplicitMigration_ boolValue];
}
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
}
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
+ (NSString *)idFor:(NSString *)userName {
return [[userName hashWith:PearlHashSHA1] encodeHex];

33
MasterPassword/MPKey.h Normal file
View File

@ -0,0 +1,33 @@
/**
* 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
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import <Foundation/Foundation.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;
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm;
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
- (BOOL)isEqualToKey:(MPKey *)key;
@end

66
MasterPassword/MPKey.m Normal file
View File

@ -0,0 +1,66 @@
/**
* 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
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPKey.h"
#import "MPAlgorithm.h"
@interface MPKey ()
@property (nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
@property (nonatomic, readwrite, strong) NSData *keyData;
@property (nonatomic, readwrite, strong) NSData *keyID;
@end
@implementation MPKey
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
if (!(self = [super init]))
return nil;
self.keyData = keyData;
self.algorithm = algorithm;
self.keyID = [self.algorithm keyIDForKeyData:keyData];
return self;
}
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange(0, MIN(subKeyLength, self.keyData.length))];
return [self.algorithm keyFromKeyData:subKeyData];
}
- (BOOL)isEqualToKey:(MPKey *)key {
return [self.keyID isEqualToData:key.keyID];
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[MPKey class]])
return NO;
return [self isEqualToKey:object];
}
@end

View File

@ -6,9 +6,7 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
@class MPElementEntity;
#import "MPKey.h"
typedef enum {
MPElementContentTypePassword,
@ -51,6 +49,7 @@ typedef enum {
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
#define MPCheckpointEditUserName @"MPCheckpointEditUserName"
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
#define MPCheckpointCloseOutdatedAlert @"MPCheckpointCloseOutdatedAlert"
#define MPCheckpointUseType @"MPCheckpointUseType"
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
@ -78,14 +77,3 @@ typedef enum {
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUpdated @"MPNotificationElementUpdated"
NSData *keyForPassword(NSString *password, NSString *username);
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
NSData *keyIDForPassword(NSString *password, NSString *username);
NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type);
NSString *NSStringShortFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);
void MPElementMigrate(MPElementEntity *element, BOOL explicit);

View File

@ -2,7 +2,7 @@
// MPUserEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 10/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -19,6 +19,7 @@
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSSet *elements;
@end

View File

@ -2,7 +2,7 @@
// MPUserEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 10/07/12.
// Created by Maarten Billemont on 17/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -18,6 +18,7 @@
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic requiresExplicitMigration_;
@dynamic elements;
@end

View File

@ -4,7 +4,7 @@
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO"/>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
@ -23,6 +23,7 @@
<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="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>

View File

@ -43,6 +43,9 @@
@property (weak, nonatomic) IBOutlet UIView *userNameContainer;
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
@property (weak, nonatomic) IBOutlet UIButton *passwordUser;
@property (weak, nonatomic) IBOutlet UIView *outdatedAlertContainer;
@property (weak, nonatomic) IBOutlet UIImageView *outdatedAlertBack;
@property (weak, nonatomic) IBOutlet UIButton *outdatedAlertCloseButton;
@property (copy) void (^contentTipCleanup)(BOOL finished);
@property (copy) void (^toolTipCleanup)(BOOL finished);
@ -56,6 +59,8 @@
- (IBAction)upgradePassword;
- (IBAction)action:(UIBarButtonItem *)sender;
- (IBAction)toggleUser;
- (IBAction)closeOutdatedAlert;
- (IBAction)infoOutdatedAlert;
- (void)toggleHelpAnimated:(BOOL)animated;
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated;

View File

@ -13,8 +13,6 @@
#import "LocalyticsSession.h"
void MPElementMigrate(MPElementEntity *entity, BOOL i);
@implementation MPMainViewController
@synthesize userNameHidden = _userNameHidden;
@synthesize activeElement = _activeElement;
@ -45,6 +43,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
@synthesize userNameContainer = _userNameContainer;
@synthesize userNameField = _userNameField;
@synthesize passwordUser = _passwordUser;
@synthesize outdatedAlertContainer = _outdatedAlertContainer;
@synthesize outdatedAlertBack = _outdatedAlertBack;
@synthesize outdatedAlertCloseButton = _outdatedAlertCloseButton;
@synthesize contentField = _contentField;
@synthesize contentTipCleanup = _contentTipCleanup, toolTipCleanup = _toolTipCleanup;
@ -81,7 +82,8 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[self.passwordIncrementer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resetPasswordCounter:)]];
[self.userNameContainer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(editUserName:)]];
[self.userNameContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(copyUserName)]];
[self.userNameContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(copyUserName:)]];
[self.outdatedAlertBack addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(searchOutdatedElements:)]];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
@ -90,13 +92,17 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.alertBody.text = nil;
self.toolTipEditIcon.hidden = YES;
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:nil
usingBlock:^(NSNotification *note) {
[MPAppDelegate get].activeUser.requiresExplicitMigration = NO;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationElementUpdated object:nil queue:nil
usingBlock:^void(NSNotification *note) {
if (self.activeElement.type & MPElementTypeClassStored
&& ![[self.activeElement.content description] length])
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
if (self.activeElement.requiresExplicitMigration)
[self showToolTip:@"Password is outdated. Tap to upgrade it." withIcon:nil];
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^void(NSNotification *note) {
@ -119,6 +125,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.searchDisplayController.searchBar.text = nil;
self.alertContainer.alpha = 0;
self.outdatedAlertContainer.alpha = 0;
self.searchTipContainer.alpha = 0;
self.actionsTipContainer.alpha = 0;
self.typeTipContainer.alpha = 0;
@ -126,6 +133,32 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[self updateAnimated:animated];
if ([MPAppDelegate get].activeUser)
[[MPAppDelegate get].managedObjectContext performBlock:^void() {
NSError *error = nil;
NSFetchRequest *migrationRequest = [NSFetchRequest
fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d", MPAlgorithmDefaultVersion];
NSArray *migrationElements = [[MPAppDelegate get].managedObjectContext executeFetchRequest:migrationRequest error:&error];
if (!migrationElements) {
err(@"While looking for elements to migrate: %@", error);
return;
}
BOOL didRequireExplicitMigration = [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration;
if (didRequireExplicitMigration)
[MPAppDelegate_Shared get].activeUser.requiresExplicitMigration = NO;
for (MPElementEntity *migrationElement in migrationElements)
if (![migrationElement migrateExplicitly:NO])
[MPAppDelegate_Shared get].activeUser.requiresExplicitMigration = YES;
if (!didRequireExplicitMigration && [MPAppDelegate_Shared get].activeUser.requiresExplicitMigration)
[UIView animateWithDuration:0.3f animations:^{
self.outdatedAlertContainer.alpha = 1;
}];
}];
[super viewWillAppear:animated];
}
@ -189,6 +222,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[self setUserNameTipBody:nil];
[self setUserNameContainer:nil];
[self setPasswordUser:nil];
[self setOutdatedAlertContainer:nil];
[self setOutdatedAlertCloseButton:nil];
[self setOutdatedAlertBack:nil];
[super viewDidUnload];
}
@ -227,9 +263,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.siteName.text = self.activeElement.name;
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
self.typeButton.alpha = self.activeElement? 1: 0;
[self.typeButton setTitle:self.activeElement.typeName
forState:UIControlStateNormal];
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter);
@ -247,7 +283,8 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.userNameField.enabled = NO;
self.userNameField.text = self.activeElement.userName;
self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName == nil));
self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName
== nil));
[self updateUserHiddenAnimated:NO];
}
@ -323,7 +360,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
PearlString(@"setClass('%@');", self.activeElement.typeClassName)];
if (error.length)
err(@"helpView.setClass: %@", error);
}
@ -436,13 +473,13 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
self.activeElement.typeName, @"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
}
- (IBAction)copyUserName {
- (IBAction)copyUserName:(UITapGestureRecognizer *)sender {
if (!self.activeElement.userName)
return;
@ -455,7 +492,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointCopyUserNameToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyUserNameToPasteboard
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
self.activeElement.typeName, @"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
@ -479,8 +516,8 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
self.activeElement.typeName,
@"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
@ -510,8 +547,8 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
self.activeElement.typeName,
@"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
@ -532,8 +569,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointEditUserName];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditUserName attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type),
self.activeElement.typeName,
@"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
@ -580,8 +616,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointEditPassword];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
self.activeElement.typeName, @"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
@ -600,19 +635,26 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
@"This upgrade improves the site's compatibility with the latest version of Master Password."
do:^{
inf(@"Explicitly migrating element: %@", self.activeElement);
MPElementMigrate(self.activeElement, YES);
[self.activeElement migrateExplicitly:YES];
[TestFlight passCheckpoint:MPCheckpointExplicitMigration];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
self.activeElement.typeName,
@"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
}];
}
- (IBAction)searchOutdatedElements:(UITapGestureRecognizer *)sender {
self.searchDisplayController.searchBar.selectedScopeButtonIndex = MPSearchScopeOutdated;
self.searchDisplayController.searchBar.searchResultsButtonSelected = YES;
[self.searchDisplayController.searchBar becomeFirstResponder];
}
- (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{
@ -625,6 +667,23 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointCloseAlert];
}
- (IBAction)closeOutdatedAlert {
[UIView animateWithDuration:0.3f animations:^{
self.outdatedAlertContainer.alpha = 0;
}];
[TestFlight passCheckpoint:MPCheckpointCloseOutdatedAlert];
}
- (IBAction)infoOutdatedAlert {
[self setHelpChapter:@"outdated"];
[self setHelpHidden:NO animated:YES];
[self closeOutdatedAlert];
[MPAppDelegate get].activeUser.requiresExplicitMigration = NO;
}
- (IBAction)action:(id)sender {
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
@ -721,6 +780,11 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[[MPAppDelegate get] signOutAnimated:YES];
break;
}
default: {
wrn(@"Unsupported action: %u", buttonIndex - [sheet firstOtherButtonIndex]);
break;
}
}
[TestFlight passCheckpoint:MPCheckpointAction];
@ -754,16 +818,16 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
@"You will need to update your account's old password to the new one."
do:^{
// Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
if ([self.activeElement.algorithm classOfType:type] != self.activeElement.typeClass)
// Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
type)
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:[self.activeElement.algorithm classNameOfType:type]
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name;
newElement.user = self.activeElement.user;
newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed;
newElement.version = self.activeElement.version;
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement;
@ -813,10 +877,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.searchDisplayController.searchBar.text = self.activeElement.name;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated object:self.activeElement];
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", self.activeElement.typeShortName)];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type),
self.activeElement.typeName,
@"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
@ -873,7 +936,11 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
inf(@"External link: %@", [request URL]);
if ([[[request URL] query] isEqualToString:@"outdated"]) {
[self searchOutdatedElements:nil];
return NO;
}
[TestFlight passCheckpoint:MPCheckpointExternalLink];
[[UIApplication sharedApplication] openURL:[request URL]];

View File

@ -75,7 +75,7 @@
} recurse:NO];
self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey;
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType];
[super viewWillAppear:animated];
}
@ -139,7 +139,7 @@
[MPAppDelegate get].activeUser.defaultType = type;
[[MPAppDelegate get] saveContext];
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType];
}
- (MPElementType)selectedType {

View File

@ -9,6 +9,11 @@
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
typedef enum {
MPSearchScopeAll,
MPSearchScopeOutdated,
} MPSearchScope;
@protocol MPSearchResultsDelegate<NSObject>
- (void)didSelectElement:(MPElementEntity *)element;

View File

@ -96,6 +96,11 @@
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
controller.searchBar.prompt = @"Enter the site's name:";
controller.searchBar.showsScopeBar = controller.searchBar.selectedScopeButtonIndex != MPSearchScopeAll;
if (controller.searchBar.showsScopeBar)
controller.searchBar.scopeButtonTitles = [NSArray arrayWithObjects:@"All", @"Outdated", nil];
else
controller.searchBar.scopeButtonTitles = nil;
[UIView animateWithDuration:0.2f animations:^{
self.searchTipContainer.alpha = 0;
@ -112,6 +117,8 @@
controller.searchBar.prompt = nil;
controller.searchBar.searchResultsButtonSelected = NO;
controller.searchBar.selectedScopeButtonIndex = MPSearchScopeAll;
controller.searchBar.showsScopeBar = NO;
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
@ -126,11 +133,41 @@
if (!controller.active)
return NO;
[self fetchData];
return YES;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
if (!controller.active)
return NO;
[self fetchData];
return YES;
}
- (void)fetchData {
assert(self.query);
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
switch ((MPSearchScope)self.searchDisplayController.searchBar.selectedScopeButtonIndex) {
case MPSearchScopeAll:
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:
@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
self.query, self.query,
NilToNSNull([MPAppDelegate get].activeUser)];
break;
case MPSearchScopeOutdated:
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:
@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@ AND requiresExplicitMigration_ == YES",
self.query, self.query,
NilToNSNull([MPAppDelegate get].activeUser)];
break;
}
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
@ -145,8 +182,6 @@
[self.tipView removeFromSuperview];
[overlay addSubview:self.tipView];
}
return YES;
}
// See MP-14, also crashes easily on internal assertions etc..
@ -305,13 +340,14 @@
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementType type = [MPAppDelegate get].activeUser.defaultType;
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmDefault classNameOfType:type]
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([MPAppDelegate get].activeUser);
element.name = siteName;
element.user = [MPAppDelegate get].activeUser;
element.type = type;
element.version = MPAlgorithmDefaultVersion;
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didSelectElement:element];
@ -353,9 +389,8 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
[TestFlight passCheckpoint:MPCheckpointDeleteElement];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(element.type), @"type",
PearlUnsignedInteger(element.version),
@"version",
element.typeName, @"type",
PearlUnsignedInteger(element.version), @"version",
nil]];
}];
}

View File

@ -664,11 +664,77 @@ L4m3P4sSw0rD</string>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view alpha="0.0" contentMode="scaleToFill" id="6wk-NU-VQG" userLabel="View - Outdated Alert">
<rect key="frame" x="10" y="230" width="300" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView contentMode="scaleToFill" image="tip_alert_black.png" id="f30-i7-VBv">
<rect key="frame" x="0.0" y="0.0" width="300" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Outdated Sites" lineBreakMode="tailTruncation" minimumFontSize="10" id="ga8-ha-4zb">
<rect key="frame" x="70" y="20" width="210" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" id="HPZ-qz-fpL">
<rect key="frame" x="20" y="60" width="260" height="130"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<string key="text">Some of your sites have outdated passwords. Tap this alert to see them.
You should upgrade these sites and update their account's passwords as soon as convenient.</string>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Mtz-t0-jov">
<rect key="frame" x="256" y="0.0" width="44" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" hint="" label="Close"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="icon_cancel.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>
<connections>
<action selector="closeOutdatedAlert" destination="PQa-Xl-A3x" eventType="touchUpInside" id="f9T-Ij-qXx"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="pjE-9n-Zaz">
<rect key="frame" x="212" y="0.0" width="44" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" hint="" label="Close"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="icon_info.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>
<connections>
<action selector="infoOutdatedAlert" destination="PQa-Xl-A3x" eventType="touchUpInside" id="U2G-GA-wT2"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Site name" showsSearchResultsButton="YES" id="qeo-n2-WVh">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<gestureRecognizers/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
<scopeButtonTitles>
<string>All</string>
<string>Outdated</string>
</scopeButtonTitles>
</searchBar>
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL" userLabel="View - Search Tip">
<rect key="frame" x="10" y="15" width="300" height="60"/>
@ -812,6 +878,9 @@ L4m3P4sSw0rD</string>
<outlet property="displayContainer" destination="g9q-4d-ZgJ" id="DC7-mb-msa"/>
<outlet property="helpContainer" destination="61G-By-qLB" id="Jwt-0d-ZAV"/>
<outlet property="helpView" destination="8FQ-x4-lR9" id="MOg-5s-kvK"/>
<outlet property="outdatedAlertBack" destination="f30-i7-VBv" id="2sX-9a-T9F"/>
<outlet property="outdatedAlertCloseButton" destination="Mtz-t0-jov" id="gjS-mo-wcQ"/>
<outlet property="outdatedAlertContainer" destination="6wk-NU-VQG" id="f48-bw-Ucx"/>
<outlet property="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
<outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
<outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/>
@ -1553,6 +1622,7 @@ Pink fluffy door frame.</string>
<image name="icon_action.png" width="32" height="32"/>
<image name="icon_cancel.png" width="32" height="32"/>
<image name="icon_edit.png" width="32" height="32"/>
<image name="icon_info.png" width="32" height="32"/>
<image name="icon_person.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/>
<image name="icon_up.png" width="32" height="32"/>
@ -1585,10 +1655,12 @@ Pink fluffy door frame.</string>
<relationships>
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="closeOutdatedAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="editUserName:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="infoOutdatedAlert"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="toggleUser"/>
<relationship kind="action" name="upgradePassword"/>
@ -1603,6 +1675,9 @@ Pink fluffy door frame.</string>
<relationship kind="outlet" name="displayContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="outdatedAlertBack" candidateClass="UIImageView"/>
<relationship kind="outlet" name="outdatedAlertCloseButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="outdatedAlertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>

View File

@ -295,6 +295,34 @@
I invite anyone with a technical background to go through these resources to make certain of the trustworthiness of Master Password.
</p>
<h3 id="outdated">Is the algorithm stable?<br />
Will my passwords ever change?</h3>
<p>
While we're very confident of the strength of the Master Password algorithm, we're also constantly keeping an eye out
for what the evolutions are of hackers' tools and capabilities. To give you the best possible protection, there is
always the possibility that we'll have to make tweaks to the Master Password algorithm in order to fend off any
attempts at breaking in.
</p>
<p>
Usually, these tweaks will be automatically applied when you install the latest version. In this case, you will notice
nothing and all you need to take away from this is that it's best to always be running the latest version of Master Password.
</p>
<p>
It is possible, however, that to apply an upgrade to your passwords, a new password will need to be set for your site's
account. In this case, Master Password will leave your passwords the way they are but give you the <em>option</em> of
upgrading your passwords when it's convenient to you. Whenever you're ready, just tap the upgrade password icon and
Master Password will show you the old password and the new one so that you can easily update your site's account.
</p>
<p>
<em>Please note</em>: if Master Password warns you that you have outdated passwords, it's best to upgrade them all
as soon as convenient. If you lose your device or data and recreate your Master Password user on another device,
Master Password can only regenerate the passwords for you that you've upgraded. iCloud/iTunes sync or exports are not
affected, so these are good ways to safely back up your passwords.
</p>
<p>
<a href="?outdated">Tap here</a> to check if you have any outdated passwords.
</p>
<h3 id="branded">This stuff is gold.<br />
I want one branded for our company.</h3>
<p>