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.

- +