diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 16182728..e6ee5826 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,7 +2,9 @@
+
+
diff --git a/External/Pearl b/External/Pearl
index 032b71a8..36320eaa 160000
--- a/External/Pearl
+++ b/External/Pearl
@@ -1 +1 @@
-Subproject commit 032b71a820d7f9526a42f51a00023e8247cd0943
+Subproject commit 36320eaa305fce113b6c12e45c99d9a61dee80b6
diff --git a/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword-iOS.xcodeproj/project.pbxproj
index ad704807..edc448cc 100644
--- a/MasterPassword-iOS.xcodeproj/project.pbxproj
+++ b/MasterPassword-iOS.xcodeproj/project.pbxproj
@@ -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 = ""; };
+ 93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = ""; };
+ 93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = ""; };
+ 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = ""; };
+ 93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = ""; };
+ 93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV1.h; sourceTree = ""; };
+ 93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = ""; };
+ 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = ""; };
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 = ""; };
- DA0757E915B3681200613FAA /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = ""; };
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 = ""; };
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = ""; };
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = ""; };
+ DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; };
+ DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; };
+ DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = ""; };
+ DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = ""; };
+ DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = ""; };
+ DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = ""; };
+ DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = ""; };
+ DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = ""; };
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = ""; };
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = ""; };
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = ""; };
@@ -1003,7 +1020,6 @@
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = ""; };
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = ""; };
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; };
- DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = ""; };
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = ""; };
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = ""; };
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = ""; };
@@ -1642,12 +1658,6 @@
DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_teal@2x.png"; sourceTree = ""; };
DAB8D6F815036BF600CED3BC /* tip_location_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_location_wood.png; sourceTree = ""; };
DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_wood@2x.png"; sourceTree = ""; };
- DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = ""; };
- DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = ""; };
- DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; };
- DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; };
- DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = ""; };
- DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = ""; };
DABB980C150FF40100B05417 /* SendToMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SendToMac-Prefix.pch"; sourceTree = ""; };
DABB980D150FF40100B05417 /* SendToMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendToMac.h; sourceTree = ""; };
DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = ""; };
@@ -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;
};
diff --git a/MasterPassword/MPAlgorithm.h b/MasterPassword/MPAlgorithm.h
new file mode 100644
index 00000000..ad085770
--- /dev/null
+++ b/MasterPassword/MPAlgorithm.h
@@ -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
+ * @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
+
+@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 MPAlgorithmForVersion(NSUInteger version);
+id MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);
diff --git a/MasterPassword/MPAlgorithm.m b/MasterPassword/MPAlgorithm.m
new file mode 100644
index 00000000..344a1bb9
--- /dev/null
+++ b/MasterPassword/MPAlgorithm.m
@@ -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
+ * @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 MPAlgorithmForVersion(NSUInteger version) {
+
+ static NSMutableDictionary *versionToAlgorithm = nil;
+ if (!versionToAlgorithm)
+ versionToAlgorithm = [NSMutableDictionary dictionary];
+
+ id algorithm = [versionToAlgorithm objectForKey:PearlUnsignedInteger(version)];
+ if (!algorithm)
+ if ((algorithm = [NSClassFromString(PearlString(@"MPAlgorithmV%u", version)) new]))
+ [versionToAlgorithm setObject:algorithm forKey:PearlUnsignedInteger(version)];
+
+ return algorithm;
+}
+
+id MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
+
+ if (PearlCFBundleVersionCompare(bundleVersion, @"1.3") == NSOrderedAscending)
+ // Pre-1.3
+ return MPAlgorithmForVersion(0);
+
+ return MPAlgorithmDefault;
+}
diff --git a/MasterPassword/MPAlgorithmV0.h b/MasterPassword/MPAlgorithmV0.h
new file mode 100644
index 00000000..1182a1e8
--- /dev/null
+++ b/MasterPassword/MPAlgorithmV0.h
@@ -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
+ * @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
+@end
diff --git a/MasterPassword/MPTypes.m b/MasterPassword/MPAlgorithmV0.m
similarity index 56%
rename from MasterPassword/MPTypes.m
rename to MasterPassword/MPAlgorithmV0.m
index 30df319a..dd77a21d 100644
--- a/MasterPassword/MPTypes.m
+++ b/MasterPassword/MPAlgorithmV0.m
@@ -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
+ * @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);
- NSDate *start = [NSDate date];
- NSData *key = [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],
- nil] N:MP_N r:MP_r p:MP_p];
+- (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 *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],
+ nil] N:MP_N r:MP_r p:MP_p];
+
+ MPKey *key = [self keyFromKeyData:keyData];
+ trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
- trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", username, password, [keyIDForKey(key) 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);
- NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
+ 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
diff --git a/MasterPassword/MPAlgorithmV1.h b/MasterPassword/MPAlgorithmV1.h
new file mode 100644
index 00000000..8ef7032f
--- /dev/null
+++ b/MasterPassword/MPAlgorithmV1.h
@@ -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
+ * @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
diff --git a/MasterPassword/MPAlgorithmV1.m b/MasterPassword/MPAlgorithmV1.m
new file mode 100644
index 00000000..a650298b
--- /dev/null
+++ b/MasterPassword/MPAlgorithmV1.m
@@ -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
+ * @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
diff --git a/MasterPassword/MPAppDelegate_Key.m b/MasterPassword/MPAppDelegate_Key.m
index 8e3b8b43..6ff670cf 100644
--- a/MasterPassword/MPAppDelegate_Key.m
+++ b/MasterPassword/MPAppDelegate_Key.m
@@ -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];
diff --git a/MasterPassword/MPAppDelegate_Shared.h b/MasterPassword/MPAppDelegate_Shared.h
index 4bc6fde8..f55d90a4 100644
--- a/MasterPassword/MPAppDelegate_Shared.h
+++ b/MasterPassword/MPAppDelegate_Shared.h
@@ -16,7 +16,7 @@
#endif
@property (strong, nonatomic) MPUserEntity *activeUser;
-@property (strong, nonatomic) NSData *key;
+@property (strong, nonatomic) MPKey *key;
+ (MPAppDelegate_Shared *)get;
diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m
index f195462c..ea876424 100644
--- a/MasterPassword/MPAppDelegate_Store.m
+++ b/MasterPassword/MPAppDelegate_Store.m
@@ -67,11 +67,11 @@
return storeManager;
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
- localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
- containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
+ localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
+ containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
#if TARGET_OS_IPHONE
- additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
- forKey:NSPersistentStoreFileProtectionKey]
+ additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
+ forKey:NSPersistentStoreFileProtectionKey]
#else
additionalStoreOptions:nil
#endif
@@ -83,9 +83,9 @@
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication] queue:nil
- usingBlock:^(NSNotification *note) {
- [storeManager checkiCloudStatus];
- }];
+ usingBlock:^(NSNotification *note) {
+ [storeManager checkiCloudStatus];
+ }];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
object:[NSApplication sharedApplication] queue:nil
@@ -96,9 +96,9 @@
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication] queue:nil
- usingBlock:^(NSNotification *note) {
- [self saveContext];
- }];
+ usingBlock:^(NSNotification *note) {
+ [self saveContext];
+ }];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication] queue:nil
@@ -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,10 +215,10 @@
if (!headerPattern || !sitePattern)
return MPImportResultInternalError;
- NSData *key = nil;
- NSString *keyIDHex = nil, *userName = nil;
- MPUserEntity *user = nil;
- BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
+ 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];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
@@ -247,20 +245,19 @@
}
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
- NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
- NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
+ NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
+ 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]];
}
}
@@ -323,25 +324,27 @@
// Import new sites.
if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
- inManagedObjectContext:self.managedObjectContext];
+ inManagedObjectContext:self.managedObjectContext];
user.name = userName;
user.keyID = [keyIDHex decodeHex];
}
for (NSArray *siteElements in importedSiteElements) {
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 uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
+ MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
+ 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: @""];
}
diff --git a/MasterPassword/MPElementEntity.h b/MasterPassword/MPElementEntity.h
index c02bf91d..5fd8e213 100644
--- a/MasterPassword/MPElementEntity.h
+++ b/MasterPassword/MPElementEntity.h
@@ -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
diff --git a/MasterPassword/MPElementEntity.m b/MasterPassword/MPElementEntity.m
index 9d11bf3c..0429acab 100644
--- a/MasterPassword/MPElementEntity.m
+++ b/MasterPassword/MPElementEntity.m
@@ -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
diff --git a/MasterPassword/MPElementGeneratedEntity.h b/MasterPassword/MPElementGeneratedEntity.h
index 95f0e029..e4ff4bc8 100644
--- a/MasterPassword/MPElementGeneratedEntity.h
+++ b/MasterPassword/MPElementGeneratedEntity.h
@@ -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.
//
diff --git a/MasterPassword/MPElementGeneratedEntity.m b/MasterPassword/MPElementGeneratedEntity.m
index 04259644..c8aa616b 100644
--- a/MasterPassword/MPElementGeneratedEntity.m
+++ b/MasterPassword/MPElementGeneratedEntity.m
@@ -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.
//
diff --git a/MasterPassword/MPElementStoredEntity.h b/MasterPassword/MPElementStoredEntity.h
index 57980279..061fec53 100644
--- a/MasterPassword/MPElementStoredEntity.h
+++ b/MasterPassword/MPElementStoredEntity.h
@@ -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.
//
diff --git a/MasterPassword/MPElementStoredEntity.m b/MasterPassword/MPElementStoredEntity.m
index e19e433b..b972de5a 100644
--- a/MasterPassword/MPElementStoredEntity.m
+++ b/MasterPassword/MPElementStoredEntity.m
@@ -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.
//
diff --git a/MasterPassword/MPEntities.h b/MasterPassword/MPEntities.h
index 5bd4636f..bc05539a 100644
--- a/MasterPassword/MPEntities.h
+++ b/MasterPassword/MPEntities.h
@@ -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 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;
diff --git a/MasterPassword/MPEntities.m b/MasterPassword/MPEntities.m
index 3ccc21da..03eb7ecf 100644
--- a/MasterPassword/MPEntities.m
+++ b/MasterPassword/MPEntities.m
@@ -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)algorithm {
+
+ return MPAlgorithmForVersion(self.version);
+}
+
- (NSUInteger)use {
self.lastUsed = [NSDate date];
@@ -73,8 +96,8 @@
}
-- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
-
+- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
+
}
- (NSString *)description {
@@ -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
@@ -103,8 +140,8 @@
}
- (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
@@ -130,12 +167,12 @@
@"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount,
nil]
- matches:nil];
+ matches:nil];
}
- (id)content {
-
- NSData *key = [MPAppDelegate get].key;
+
+ MPKey *key = [MPAppDelegate get].key;
if (!key)
return nil;
@@ -143,18 +180,18 @@
}
- (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,25 +199,29 @@
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,
- #endif
- nil]];
+ withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
+ encryptedContent, (__bridge id)kSecValueData,
+ #if TARGET_OS_IPHONE
+ (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
+ (__bridge id)kSecAttrAccessible,
+ #endif
+ nil]];
self.contentObject = nil;
} else
self.contentObject = encryptedContent;
@@ -196,8 +237,8 @@
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];
diff --git a/MasterPassword/MPKey.h b/MasterPassword/MPKey.h
new file mode 100644
index 00000000..037d7887
--- /dev/null
+++ b/MasterPassword/MPKey.h
@@ -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
+ * @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
+
+@protocol MPAlgorithm;
+
+
+@interface MPKey : NSObject
+
+@property (nonatomic, readonly, strong) id algorithm;
+@property (nonatomic, readonly, strong) NSData *keyData;
+@property (nonatomic, readonly, strong) NSData *keyID;
+
+- (id)initWithKeyData:(NSData *)keyData algorithm:(id)algorithm;
+- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
+- (BOOL)isEqualToKey:(MPKey *)key;
+
+@end
diff --git a/MasterPassword/MPKey.m b/MasterPassword/MPKey.m
new file mode 100644
index 00000000..690a7968
--- /dev/null
+++ b/MasterPassword/MPKey.m
@@ -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
+ * @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 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)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
diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h
index 47794e17..c2892868 100644
--- a/MasterPassword/MPTypes.h
+++ b/MasterPassword/MPTypes.h
@@ -6,9 +6,7 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
-#import
-
-@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);
diff --git a/MasterPassword/MPUserEntity.h b/MasterPassword/MPUserEntity.h
index ff766e6c..0bcbb6ff 100644
--- a/MasterPassword/MPUserEntity.h
+++ b/MasterPassword/MPUserEntity.h
@@ -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
diff --git a/MasterPassword/MPUserEntity.m b/MasterPassword/MPUserEntity.m
index a89b4949..e584c806 100644
--- a/MasterPassword/MPUserEntity.m
+++ b/MasterPassword/MPUserEntity.m
@@ -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
diff --git a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents
index db179f25..3c3e90de 100644
--- a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents
+++ b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents
@@ -4,7 +4,7 @@
-
+
@@ -23,6 +23,7 @@
+
diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h
index 2056b5ef..b59122f8 100644
--- a/MasterPassword/iOS/MPMainViewController.h
+++ b/MasterPassword/iOS/MPMainViewController.h
@@ -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;
diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m
index a846b43f..30a45e2f 100644
--- a/MasterPassword/iOS/MPMainViewController.m
+++ b/MasterPassword/iOS/MPMainViewController.m
@@ -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) {
@@ -118,14 +124,41 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil;
- self.alertContainer.alpha = 0;
- self.searchTipContainer.alpha = 0;
- self.actionsTipContainer.alpha = 0;
- self.typeTipContainer.alpha = 0;
- self.toolTipContainer.alpha = 0;
+ self.alertContainer.alpha = 0;
+ self.outdatedAlertContainer.alpha = 0;
+ self.searchTipContainer.alpha = 0;
+ self.actionsTipContainer.alpha = 0;
+ self.typeTipContainer.alpha = 0;
+ self.toolTipContainer.alpha = 0;
[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];
}
@@ -208,7 +244,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.passwordIncrementer.alpha = 0;
self.passwordEdit.alpha = 0;
self.passwordUpgrade.alpha = 0;
- self.passwordUser.alpha = 0;
+ self.passwordUser.alpha = 0;
if (self.activeElement)
self.passwordUser.alpha = 0.5f;
@@ -224,18 +260,18 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
if (self.activeElement.type & MPElementTypeClassStored)
self.passwordEdit.alpha = 0.5f;
}
-
+
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);
- self.contentField.enabled = NO;
- self.contentField.text = @"";
+ self.contentField.enabled = NO;
+ self.contentField.text = @"";
if (self.activeElement.name && ![self.activeElement isDeleted])
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [self.activeElement.content description];
@@ -246,8 +282,9 @@ 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.userNameField.text = self.activeElement.userName;
+ self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName
+ == nil));
[self updateUserHiddenAnimated:NO];
}
@@ -263,20 +300,20 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
}
- (void)updateHelpHiddenAnimated:(BOOL)animated {
-
+
if (animated) {
[UIView animateWithDuration:0.3f animations:^{
[self updateHelpHiddenAnimated:NO];
}];
return;
}
-
+
if ([[MPiOSConfig get].helpHidden boolValue]) {
- self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
- self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
+ self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44);
+ self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
} else {
- self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225);
- self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266);
+ self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225);
+ self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266);
}
}
@@ -302,9 +339,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
}
if (self.userNameHidden) {
- self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87);
+ self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87);
} else {
- self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 137);
+ self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 137);
}
}
@@ -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]];
@@ -523,7 +560,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
if (sender.state != UIGestureRecognizerStateBegan)
// Only fire when the gesture was first detected.
return;
-
+
if (!self.activeElement)
return;
@@ -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",
@@ -854,7 +917,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
}
if (textField == self.userNameField) {
- self.userNameField.enabled = NO;
+ self.userNameField.enabled = NO;
if (![[MPiOSConfig get].userNameTipShown boolValue]) {
[self showUserNameTip:@"Tap to copy or hold to edit."];
[MPiOSConfig get].userNameTipShown = PearlBool(YES);
@@ -864,7 +927,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.activeElement.userName = self.userNameField.text;
else
self.activeElement.userName = nil;
-
+
[[MPAppDelegate get] saveContext];
}
}
@@ -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]];
diff --git a/MasterPassword/iOS/MPPreferencesViewController.m b/MasterPassword/iOS/MPPreferencesViewController.m
index 040245bd..e8a87b13 100644
--- a/MasterPassword/iOS/MPPreferencesViewController.m
+++ b/MasterPassword/iOS/MPPreferencesViewController.m
@@ -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 {
diff --git a/MasterPassword/iOS/MPSearchDelegate.h b/MasterPassword/iOS/MPSearchDelegate.h
index beb9b54c..2caa2ef5 100644
--- a/MasterPassword/iOS/MPSearchDelegate.h
+++ b/MasterPassword/iOS/MPSearchDelegate.h
@@ -9,6 +9,11 @@
#import
#import "MPElementEntity.h"
+typedef enum {
+ MPSearchScopeAll,
+ MPSearchScopeOutdated,
+} MPSearchScope;
+
@protocol MPSearchResultsDelegate
- (void)didSelectElement:(MPElementEntity *)element;
diff --git a/MasterPassword/iOS/MPSearchDelegate.m b/MasterPassword/iOS/MPSearchDelegate.m
index 7060683d..e5dabde5 100644
--- a/MasterPassword/iOS/MPSearchDelegate.m
+++ b/MasterPassword/iOS/MPSearchDelegate.m
@@ -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;
@@ -104,14 +109,16 @@
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
- controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
- self.query = @"";
+ controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
+ self.query = @"";
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
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 == %@",
- self.query, self.query,
- NilToNSNull([MPAppDelegate get].activeUser)];
+ 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.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]];
}];
}
diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard
index cf8718c1..350e488a 100644
--- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard
+++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard
@@ -664,11 +664,77 @@ L4m3P4sSw0rD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ All
+ Outdated
+
@@ -812,6 +878,9 @@ L4m3P4sSw0rD
+
+
+
@@ -1553,6 +1622,7 @@ Pink fluffy door frame.
+
@@ -1585,10 +1655,12 @@ Pink fluffy door frame.
+
+
@@ -1603,6 +1675,9 @@ Pink fluffy door frame.
+
+
+
diff --git a/Resources/help.html b/Resources/help.html
index 2cd8073d..9eb5ac78 100644
--- a/Resources/help.html
+++ b/Resources/help.html
@@ -55,7 +55,7 @@
$(".Class").css("display", "none");
if (!$(".Class." + activeClass).length)
return "Not found: " + activeClass;
-
+
$(".Class." + activeClass).css("display", "block");
}
@@ -75,13 +75,13 @@
While searching , the names of previously used sites will be listed.
Tap one of these results to go straight to its password.
-
+
— 2 —
The site 's password is now displayed.
Tap it to copy the password . Once copied, you can switch to another application and paste it into a password field.
-
+
To change the password for this site, tap the edit icon .
@@ -90,7 +90,7 @@
Below the password you can set the password type . Some types create a password for you ,
others let you choose your own .
-
+
If the site complains when you try to set or update the password, try changing the password type.
@@ -173,7 +173,7 @@
share the password with anyone else. Instead, the app creates secure passwords for use with whatever site
or purpose you might need a password for.
-
+
I can't change all my passwords.
Some of them were assigned to me.
@@ -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.
+ Is the algorithm stable?
+ Will my passwords ever change?
+
+ 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.
+
+
+ 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.
+
+
+ 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 option 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.
+
+
+ Please note : 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.
+
+
+ Tap here to check if you have any outdated passwords.
+
+
This stuff is gold.
I want one branded for our company.
@@ -304,7 +332,7 @@
Master Password can also be used as a One-Time Password token generator to secure your infrastructure and client access.
-
+