Better abstraction for key & algorithm + V1
[IMPROVED]  A master password key is now better abstracted in an object.
[IMPROVED]  A master password algorithm is now better astracted in an
            object.
[ADDED]     Elements now have a specific algorithm version.
[ADDED]     Automatic/explicit migration of elements.
[ADDED]     Searching outdated elements.
			
			
This commit is contained in:
		
							
								
								
									
										2
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							@@ -2,7 +2,9 @@
 | 
			
		||||
  <profile version="1.0" is_locked="false">
 | 
			
		||||
    <option name="myName" value="Project Default" />
 | 
			
		||||
    <option name="myLocal" value="false" />
 | 
			
		||||
    <inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
 | 
			
		||||
    <inspection_tool class="MethodIsLaterInTheScope" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
    <inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
 | 
			
		||||
  </profile>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								External/Pearl
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								External/Pearl
									
									
									
									
										vendored
									
									
								
							 Submodule External/Pearl updated: 032b71a820...36320eaa30
									
								
							@@ -7,13 +7,20 @@
 | 
			
		||||
	objects = {
 | 
			
		||||
 | 
			
		||||
/* Begin PBXBuildFile section */
 | 
			
		||||
		93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E81EFABC6085AC8AE69 /* MPKey.m */; };
 | 
			
		||||
		93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */; };
 | 
			
		||||
		93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */; };
 | 
			
		||||
		93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; };
 | 
			
		||||
		DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
 | 
			
		||||
		DA0757EA15B3681200613FAA /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0757E915B3681200613FAA /* MPElementEntity.m */; };
 | 
			
		||||
		DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
 | 
			
		||||
		DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
 | 
			
		||||
		DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
 | 
			
		||||
		DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
 | 
			
		||||
		DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
 | 
			
		||||
		DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3215B55397007ED9BC /* MPUserEntity.m */; };
 | 
			
		||||
		DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */; };
 | 
			
		||||
		DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */; };
 | 
			
		||||
		DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */; };
 | 
			
		||||
		DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
 | 
			
		||||
		DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
 | 
			
		||||
		DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
 | 
			
		||||
@@ -101,7 +108,6 @@
 | 
			
		||||
		DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
 | 
			
		||||
		DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
 | 
			
		||||
		DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
 | 
			
		||||
		DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
 | 
			
		||||
		DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
 | 
			
		||||
		DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
 | 
			
		||||
		DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
 | 
			
		||||
@@ -673,9 +679,6 @@
 | 
			
		||||
		DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B515036BF600CED3BC /* lock_red@2x.png */; };
 | 
			
		||||
		DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B615036BF600CED3BC /* logo-bare.png */; };
 | 
			
		||||
		DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
 | 
			
		||||
		DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */; };
 | 
			
		||||
		DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */; };
 | 
			
		||||
		DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */; };
 | 
			
		||||
		DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
 | 
			
		||||
		DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
 | 
			
		||||
		DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
 | 
			
		||||
@@ -887,9 +890,15 @@
 | 
			
		||||
/* End PBXContainerItemProxy section */
 | 
			
		||||
 | 
			
		||||
/* Begin PBXFileReference section */
 | 
			
		||||
		93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = "<group>"; };
 | 
			
		||||
		93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = "<group>"; };
 | 
			
		||||
		93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = "<group>"; };
 | 
			
		||||
		93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = "<group>"; };
 | 
			
		||||
		93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
 | 
			
		||||
		93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV1.h; sourceTree = "<group>"; };
 | 
			
		||||
		93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
 | 
			
		||||
		93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
 | 
			
		||||
		DA0757E815B3681200613FAA /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DA0757E915B3681200613FAA /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA0A1D0315690A9A0092735D /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = Resources/Default.png; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		DA0A1D0415690A9A0092735D /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "Resources/Default@2x.png"; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		DA0A1D0715690AD40092735D /* tip_arrow_banana.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = tip_arrow_banana.png; path = Resources/Tooltips/tip_arrow_banana.png; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
@@ -902,6 +911,14 @@
 | 
			
		||||
		DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
 | 
			
		||||
		DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
 | 
			
		||||
		DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
 | 
			
		||||
		DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
 | 
			
		||||
		DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
 | 
			
		||||
@@ -1003,7 +1020,6 @@
 | 
			
		||||
		DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
 | 
			
		||||
@@ -1642,12 +1658,6 @@
 | 
			
		||||
		DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_teal@2x.png"; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D6F815036BF600CED3BC /* tip_location_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_location_wood.png; sourceTree = "<group>"; };
 | 
			
		||||
		DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_wood@2x.png"; sourceTree = "<group>"; };
 | 
			
		||||
		DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
 | 
			
		||||
		DABB980C150FF40100B05417 /* SendToMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SendToMac-Prefix.pch"; sourceTree = "<group>"; };
 | 
			
		||||
		DABB980D150FF40100B05417 /* SendToMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendToMac.h; sourceTree = "<group>"; };
 | 
			
		||||
		DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = "<group>"; };
 | 
			
		||||
@@ -1999,17 +2009,25 @@
 | 
			
		||||
		DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */,
 | 
			
		||||
				DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */,
 | 
			
		||||
				DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */,
 | 
			
		||||
				DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */,
 | 
			
		||||
				DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */,
 | 
			
		||||
				DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */,
 | 
			
		||||
				93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */,
 | 
			
		||||
				93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */,
 | 
			
		||||
				93D39B0DF5E3C56355186738 /* MPAlgorithm.m */,
 | 
			
		||||
				93D39C68AFA48A13015E4FAC /* MPKey.h */,
 | 
			
		||||
				93D39E81EFABC6085AC8AE69 /* MPKey.m */,
 | 
			
		||||
				93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */,
 | 
			
		||||
				93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */,
 | 
			
		||||
				93D398E394E311C545E0A057 /* MPAlgorithm.h */,
 | 
			
		||||
				DA0E07941577FE490008A67E /* MPEntities.h */,
 | 
			
		||||
				DA0E07951577FE490008A67E /* MPEntities.m */,
 | 
			
		||||
				DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
 | 
			
		||||
				DA0757E815B3681200613FAA /* MPElementEntity.h */,
 | 
			
		||||
				DA0757E915B3681200613FAA /* MPElementEntity.m */,
 | 
			
		||||
				DA0F9F3115B55397007ED9BC /* MPUserEntity.h */,
 | 
			
		||||
				DA0F9F3215B55397007ED9BC /* MPUserEntity.m */,
 | 
			
		||||
				DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */,
 | 
			
		||||
				DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */,
 | 
			
		||||
				DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */,
 | 
			
		||||
				DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */,
 | 
			
		||||
				DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */,
 | 
			
		||||
				DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */,
 | 
			
		||||
				DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
 | 
			
		||||
				DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
 | 
			
		||||
				DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
 | 
			
		||||
@@ -2019,7 +2037,6 @@
 | 
			
		||||
				DA600C2615056427008E9AB6 /* MPConfig.h */,
 | 
			
		||||
				DA600C2715056427008E9AB6 /* MPConfig.m */,
 | 
			
		||||
				DAB8D45915036BCF00CED3BC /* MPTypes.h */,
 | 
			
		||||
				DAB8D45615036BCF00CED3BC /* MPTypes.m */,
 | 
			
		||||
				DAB8D43E15036BCF00CED3BC /* iOS */,
 | 
			
		||||
			);
 | 
			
		||||
			path = MasterPassword;
 | 
			
		||||
@@ -4238,17 +4255,20 @@
 | 
			
		||||
				DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
 | 
			
		||||
				DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
 | 
			
		||||
				DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
 | 
			
		||||
				DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
 | 
			
		||||
				DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
 | 
			
		||||
				DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
 | 
			
		||||
				DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
 | 
			
		||||
				DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
 | 
			
		||||
				DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
 | 
			
		||||
				DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
 | 
			
		||||
				DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */,
 | 
			
		||||
				DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */,
 | 
			
		||||
				DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */,
 | 
			
		||||
				DA0757EA15B3681200613FAA /* MPElementEntity.m in Sources */,
 | 
			
		||||
				93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */,
 | 
			
		||||
				93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */,
 | 
			
		||||
				93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */,
 | 
			
		||||
				93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */,
 | 
			
		||||
				DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */,
 | 
			
		||||
				DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */,
 | 
			
		||||
				DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */,
 | 
			
		||||
				DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								MasterPassword/MPAlgorithm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								MasterPassword/MPAlgorithm.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPAlgorithm
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 16/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPKey.h"
 | 
			
		||||
#import "MPElementGeneratedEntity.h"
 | 
			
		||||
 | 
			
		||||
#define MPAlgorithmDefaultVersion 1
 | 
			
		||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
 | 
			
		||||
 | 
			
		||||
@protocol MPAlgorithm<NSObject>
 | 
			
		||||
 | 
			
		||||
@required
 | 
			
		||||
- (NSUInteger)version;
 | 
			
		||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
 | 
			
		||||
 | 
			
		||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
 | 
			
		||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
 | 
			
		||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
 | 
			
		||||
 | 
			
		||||
- (NSString *)nameOfType:(MPElementType)type;
 | 
			
		||||
- (NSString *)shortNameOfType:(MPElementType)type;
 | 
			
		||||
- (NSString *)classNameOfType:(MPElementType)type;
 | 
			
		||||
- (Class)classOfType:(MPElementType)type;
 | 
			
		||||
 | 
			
		||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
 | 
			
		||||
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);
 | 
			
		||||
							
								
								
									
										42
									
								
								MasterPassword/MPAlgorithm.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								MasterPassword/MPAlgorithm.m
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPAlgorithm
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 16/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPAlgorithm.h"
 | 
			
		||||
#import "MPEntities.h"
 | 
			
		||||
 | 
			
		||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
 | 
			
		||||
 | 
			
		||||
    static NSMutableDictionary *versionToAlgorithm = nil;
 | 
			
		||||
    if (!versionToAlgorithm)
 | 
			
		||||
        versionToAlgorithm = [NSMutableDictionary dictionary];
 | 
			
		||||
 | 
			
		||||
    id<MPAlgorithm> algorithm = [versionToAlgorithm objectForKey:PearlUnsignedInteger(version)];
 | 
			
		||||
    if (!algorithm)
 | 
			
		||||
        if ((algorithm = [NSClassFromString(PearlString(@"MPAlgorithmV%u", version)) new]))
 | 
			
		||||
            [versionToAlgorithm setObject:algorithm forKey:PearlUnsignedInteger(version)];
 | 
			
		||||
 | 
			
		||||
    return algorithm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
 | 
			
		||||
 | 
			
		||||
    if (PearlCFBundleVersionCompare(bundleVersion, @"1.3") == NSOrderedAscending)
 | 
			
		||||
        // Pre-1.3
 | 
			
		||||
        return MPAlgorithmForVersion(0);
 | 
			
		||||
 | 
			
		||||
    return MPAlgorithmDefault;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								MasterPassword/MPAlgorithmV0.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								MasterPassword/MPAlgorithmV0.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPAlgorithmV0
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 16/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPAlgorithm.h"
 | 
			
		||||
 | 
			
		||||
@interface MPAlgorithmV0 : NSObject <MPAlgorithm>
 | 
			
		||||
@end
 | 
			
		||||
@@ -1,54 +1,83 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPTypes.m
 | 
			
		||||
//  MasterPassword
 | 
			
		||||
//  MPAlgorithmV0
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 02/01/12.
 | 
			
		||||
//  Copyright (c) 2012 Lyndir. All rights reserved.
 | 
			
		||||
//  Created by Maarten Billemont on 16/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPAlgorithmV0.h"
 | 
			
		||||
#import "MPEntities.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define MP_N        32768
 | 
			
		||||
#define MP_r        8
 | 
			
		||||
#define MP_p        2
 | 
			
		||||
#define MP_dkLen    64
 | 
			
		||||
#define MP_hash     PearlHashSHA256
 | 
			
		||||
 | 
			
		||||
NSData *keyForPassword(NSString *password, NSString *username) {
 | 
			
		||||
@implementation MPAlgorithmV0
 | 
			
		||||
 | 
			
		||||
    uint32_t nusernameLength = htonl(username.length);
 | 
			
		||||
    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
 | 
			
		||||
							
								
								
									
										22
									
								
								MasterPassword/MPAlgorithmV1.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								MasterPassword/MPAlgorithmV1.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPAlgorithmV1
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 17/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPAlgorithmV0.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@interface MPAlgorithmV1 : MPAlgorithmV0
 | 
			
		||||
@end
 | 
			
		||||
							
								
								
									
										113
									
								
								MasterPassword/MPAlgorithmV1.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								MasterPassword/MPAlgorithmV1.m
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPAlgorithmV1
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 17/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPAlgorithmV1.h"
 | 
			
		||||
#import "MPEntities.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@implementation MPAlgorithmV1
 | 
			
		||||
 | 
			
		||||
- (NSUInteger)version {
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
 | 
			
		||||
 | 
			
		||||
    if (element.version != [self version] - 1)
 | 
			
		||||
     // Only migrate from previous version.
 | 
			
		||||
        return NO;
 | 
			
		||||
 | 
			
		||||
    if (!explicit) {
 | 
			
		||||
        if (element.type & MPElementTypeClassGenerated) {
 | 
			
		||||
            // This migration requires explicit permission for types of the generated class.
 | 
			
		||||
            element.requiresExplicitMigration = YES;
 | 
			
		||||
            return NO;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Apply migration.
 | 
			
		||||
    element.requiresExplicitMigration = NO;
 | 
			
		||||
    element.version                   = [self version];
 | 
			
		||||
    return YES;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    static NSDictionary *MPTypes_ciphers = nil;
 | 
			
		||||
 | 
			
		||||
    if (!element)
 | 
			
		||||
        return nil;
 | 
			
		||||
 | 
			
		||||
    if (!(element.type & MPElementTypeClassGenerated)) {
 | 
			
		||||
        err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", [self nameOfType:element.type], element.name);
 | 
			
		||||
        return nil;
 | 
			
		||||
    }
 | 
			
		||||
    if (!element.name.length) {
 | 
			
		||||
        err(@"Missing name.");
 | 
			
		||||
        return nil;
 | 
			
		||||
    }
 | 
			
		||||
    if (!key.keyData.length) {
 | 
			
		||||
        err(@"Missing key.");
 | 
			
		||||
        return nil;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (MPTypes_ciphers == nil)
 | 
			
		||||
        MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
 | 
			
		||||
                                                                                            withExtension:@"plist"]];
 | 
			
		||||
 | 
			
		||||
    // Determine the seed whose bytes will be used for calculating a password
 | 
			
		||||
    uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
 | 
			
		||||
    NSData *counterBytes    = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
 | 
			
		||||
    NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
 | 
			
		||||
    trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], element.name, [counterBytes encodeHex]);
 | 
			
		||||
    NSData *seed = [[NSData dataByConcatenatingDatas:
 | 
			
		||||
                             [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
 | 
			
		||||
                             nameLengthBytes,
 | 
			
		||||
                             [element.name dataUsingEncoding:NSUTF8StringEncoding],
 | 
			
		||||
                             counterBytes,
 | 
			
		||||
                             nil]
 | 
			
		||||
                             hmacWith:PearlHashSHA256 key:key.keyData];
 | 
			
		||||
    trc(@"seed is: %@", [seed encodeBase64]);
 | 
			
		||||
    const unsigned char *seedBytes = seed.bytes;
 | 
			
		||||
 | 
			
		||||
    // Determine the cipher from the first seed byte.
 | 
			
		||||
    assert([seed length]);
 | 
			
		||||
    NSArray  *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
 | 
			
		||||
                                              valueForKey:[self nameOfType:element.type]];
 | 
			
		||||
    NSString *cipher      = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
 | 
			
		||||
    trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:element.type], typeCiphers, cipher);
 | 
			
		||||
 | 
			
		||||
    // Encode the content, character by character, using subsequent seed bytes and the cipher.
 | 
			
		||||
    assert([seed length] >= [cipher length] + 1);
 | 
			
		||||
    NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
 | 
			
		||||
    for (NSUInteger c = 0; c < [cipher length]; ++c) {
 | 
			
		||||
        uint16_t keyByte = seedBytes[c + 1];
 | 
			
		||||
        NSString *cipherClass           = [cipher substringWithRange:NSMakeRange(c, 1)];
 | 
			
		||||
        NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
 | 
			
		||||
        NSString *character             = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
 | 
			
		||||
                                                                                                1)];
 | 
			
		||||
 | 
			
		||||
        trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
 | 
			
		||||
        [content appendString:character];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
@@ -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];
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@property (strong, nonatomic) MPUserEntity *activeUser;
 | 
			
		||||
@property (strong, nonatomic) NSData       *key;
 | 
			
		||||
@property (strong, nonatomic) MPKey        *key;
 | 
			
		||||
 | 
			
		||||
+ (MPAppDelegate_Shared *)get;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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: @""];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,20 +11,27 @@
 | 
			
		||||
#import "MPElementStoredEntity.h"
 | 
			
		||||
#import "MPElementGeneratedEntity.h"
 | 
			
		||||
#import "MPUserEntity.h"
 | 
			
		||||
#import "MPAlgorithm.h"
 | 
			
		||||
 | 
			
		||||
#define MPAvatarCount 19
 | 
			
		||||
 | 
			
		||||
@interface MPElementEntity (MP)
 | 
			
		||||
 | 
			
		||||
@property (assign) MPElementType type;
 | 
			
		||||
@property (readonly) NSString *typeName;
 | 
			
		||||
@property (readonly) NSString *typeShortName;
 | 
			
		||||
@property (readonly) NSString *typeClassName;
 | 
			
		||||
@property (readonly) Class typeClass;
 | 
			
		||||
@property (assign) NSUInteger    uses;
 | 
			
		||||
@property (assign) NSUInteger    version;
 | 
			
		||||
@property (assign) BOOL          requiresExplicitMigration;
 | 
			
		||||
@property (readonly) id<MPAlgorithm> algorithm;
 | 
			
		||||
 | 
			
		||||
- (NSUInteger)use;
 | 
			
		||||
- (NSString *)exportContent;
 | 
			
		||||
- (void)importProtectedContent:(NSString *)protectedContent;
 | 
			
		||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
 | 
			
		||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
 | 
			
		||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +46,7 @@
 | 
			
		||||
@property (assign) NSUInteger avatar;
 | 
			
		||||
@property (assign) BOOL       saveKey;
 | 
			
		||||
@property (assign) MPElementType defaultType;
 | 
			
		||||
@property (assign) BOOL          requiresExplicitMigration;
 | 
			
		||||
@property (readonly) NSString *userID;
 | 
			
		||||
 | 
			
		||||
+ (NSString *)idFor:(NSString *)userName;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,6 @@
 | 
			
		||||
 | 
			
		||||
#import "MPEntities.h"
 | 
			
		||||
#import "MPAppDelegate.h"
 | 
			
		||||
#import "MPAppDelegate_Key.h"
 | 
			
		||||
#import "MPUserEntity.h"
 | 
			
		||||
 | 
			
		||||
@implementation MPElementEntity (MP)
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +21,26 @@
 | 
			
		||||
    self.type_ = PearlUnsignedInteger(aType);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)typeName {
 | 
			
		||||
 | 
			
		||||
    return [self.algorithm nameOfType:self.type];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)typeShortName {
 | 
			
		||||
 | 
			
		||||
    return [self.algorithm shortNameOfType:self.type];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)typeClassName {
 | 
			
		||||
 | 
			
		||||
    return [self.algorithm classNameOfType:self.type];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (Class)typeClass {
 | 
			
		||||
 | 
			
		||||
    return [self.algorithm classOfType:self.type];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSUInteger)uses {
 | 
			
		||||
 | 
			
		||||
    return [self.uses_ unsignedIntegerValue];
 | 
			
		||||
@@ -53,6 +71,11 @@
 | 
			
		||||
    self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (id<MPAlgorithm>)algorithm {
 | 
			
		||||
 | 
			
		||||
    return MPAlgorithmForVersion(self.version);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSUInteger)use {
 | 
			
		||||
 | 
			
		||||
    self.lastUsed = [NSDate date];
 | 
			
		||||
@@ -73,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];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								MasterPassword/MPKey.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								MasterPassword/MPKey.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPKey
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 16/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
 | 
			
		||||
@protocol MPAlgorithm;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@interface MPKey : NSObject
 | 
			
		||||
 | 
			
		||||
@property (nonatomic, readonly, strong) id<MPAlgorithm> algorithm;
 | 
			
		||||
@property (nonatomic, readonly, strong) NSData *keyData;
 | 
			
		||||
@property (nonatomic, readonly, strong) NSData *keyID;
 | 
			
		||||
 | 
			
		||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm;
 | 
			
		||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
 | 
			
		||||
- (BOOL)isEqualToKey:(MPKey *)key;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
							
								
								
									
										66
									
								
								MasterPassword/MPKey.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								MasterPassword/MPKey.m
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
 | 
			
		||||
 *
 | 
			
		||||
 * See the enclosed file LICENSE for license information (LGPLv3). If you did
 | 
			
		||||
 * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 *
 | 
			
		||||
 * @author   Maarten Billemont <lhunath@lyndir.com>
 | 
			
		||||
 * @license  http://www.gnu.org/licenses/lgpl-3.0.txt
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  MPKey
 | 
			
		||||
//
 | 
			
		||||
//  Created by Maarten Billemont on 16/07/12.
 | 
			
		||||
//  Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import "MPKey.h"
 | 
			
		||||
#import "MPAlgorithm.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@interface MPKey ()
 | 
			
		||||
 | 
			
		||||
@property (nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
 | 
			
		||||
@property (nonatomic, readwrite, strong) NSData *keyData;
 | 
			
		||||
@property (nonatomic, readwrite, strong) NSData *keyID;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@implementation MPKey
 | 
			
		||||
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
 | 
			
		||||
 | 
			
		||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
 | 
			
		||||
 | 
			
		||||
    if (!(self = [super init]))
 | 
			
		||||
        return  nil;
 | 
			
		||||
 | 
			
		||||
    self.keyData = keyData;
 | 
			
		||||
    self.algorithm = algorithm;
 | 
			
		||||
    self.keyID = [self.algorithm keyIDForKeyData:keyData];
 | 
			
		||||
 | 
			
		||||
    return self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
 | 
			
		||||
 | 
			
		||||
    NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange(0, MIN(subKeyLength, self.keyData.length))];
 | 
			
		||||
 | 
			
		||||
    return [self.algorithm keyFromKeyData:subKeyData];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (BOOL)isEqualToKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return [self.keyID isEqualToData:key.keyID];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (BOOL)isEqual:(id)object {
 | 
			
		||||
 | 
			
		||||
    if (![object isKindOfClass:[MPKey class]])
 | 
			
		||||
        return NO;
 | 
			
		||||
 | 
			
		||||
    return [self isEqualToKey:object];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
@@ -6,9 +6,7 @@
 | 
			
		||||
//  Copyright (c) 2012 Lyndir. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
 | 
			
		||||
@class MPElementEntity;
 | 
			
		||||
#import "MPKey.h"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    MPElementContentTypePassword,
 | 
			
		||||
@@ -51,6 +49,7 @@ typedef enum {
 | 
			
		||||
#define MPCheckpointEditPassword              @"MPCheckpointEditPassword"
 | 
			
		||||
#define MPCheckpointEditUserName              @"MPCheckpointEditUserName"
 | 
			
		||||
#define MPCheckpointCloseAlert                @"MPCheckpointCloseAlert"
 | 
			
		||||
#define MPCheckpointCloseOutdatedAlert        @"MPCheckpointCloseOutdatedAlert"
 | 
			
		||||
#define MPCheckpointUseType                   @"MPCheckpointUseType"
 | 
			
		||||
#define MPCheckpointDeleteElement             @"MPCheckpointDeleteElement"
 | 
			
		||||
#define MPCheckpointCancelSearch              @"MPCheckpointCancelSearch"
 | 
			
		||||
@@ -78,14 +77,3 @@ typedef enum {
 | 
			
		||||
#define MPNotificationSignedOut               @"MPNotificationKeyUnset"
 | 
			
		||||
#define MPNotificationKeyForgotten            @"MPNotificationKeyForgotten"
 | 
			
		||||
#define MPNotificationElementUpdated          @"MPNotificationElementUpdated"
 | 
			
		||||
 | 
			
		||||
NSData   *keyForPassword(NSString *password, NSString *username);
 | 
			
		||||
NSData   *subkeyForKey(NSData *key, NSUInteger subkeyLength);
 | 
			
		||||
NSData   *keyIDForPassword(NSString *password, NSString *username);
 | 
			
		||||
NSData   *keyIDForKey(NSData *key);
 | 
			
		||||
NSString *NSStringFromMPElementType(MPElementType type);
 | 
			
		||||
NSString *NSStringShortFromMPElementType(MPElementType type);
 | 
			
		||||
NSString *ClassNameFromMPElementType(MPElementType type);
 | 
			
		||||
Class    ClassFromMPElementType(MPElementType type);
 | 
			
		||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);
 | 
			
		||||
void     MPElementMigrate(MPElementEntity *element, BOOL explicit);
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
        <attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
 | 
			
		||||
        <attribute name="lastUsed" attributeType="Date" syncable="YES"/>
 | 
			
		||||
        <attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
 | 
			
		||||
        <attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
 | 
			
		||||
        <attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO"/>
 | 
			
		||||
        <attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
 | 
			
		||||
        <attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
 | 
			
		||||
        <attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
        <attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
 | 
			
		||||
        <attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
 | 
			
		||||
        <attribute name="name" attributeType="String" syncable="YES"/>
 | 
			
		||||
        <attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
 | 
			
		||||
        <attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
 | 
			
		||||
        <relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
 | 
			
		||||
    </entity>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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]];
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,11 @@
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#import "MPElementEntity.h"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    MPSearchScopeAll,
 | 
			
		||||
    MPSearchScopeOutdated,
 | 
			
		||||
} MPSearchScope;
 | 
			
		||||
 | 
			
		||||
@protocol MPSearchResultsDelegate<NSObject>
 | 
			
		||||
 | 
			
		||||
- (void)didSelectElement:(MPElementEntity *)element;
 | 
			
		||||
 
 | 
			
		||||
@@ -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]];
 | 
			
		||||
            }];
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -664,11 +664,77 @@ L4m3P4sSw0rD</string>
 | 
			
		||||
                                </subviews>
 | 
			
		||||
                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
 | 
			
		||||
                            </view>
 | 
			
		||||
                            <view alpha="0.0" contentMode="scaleToFill" id="6wk-NU-VQG" userLabel="View - Outdated Alert">
 | 
			
		||||
                                <rect key="frame" x="10" y="230" width="300" height="180"/>
 | 
			
		||||
                                <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
 | 
			
		||||
                                <subviews>
 | 
			
		||||
                                    <imageView contentMode="scaleToFill" image="tip_alert_black.png" id="f30-i7-VBv">
 | 
			
		||||
                                        <rect key="frame" x="0.0" y="0.0" width="300" height="180"/>
 | 
			
		||||
                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
 | 
			
		||||
                                        <rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/>
 | 
			
		||||
                                    </imageView>
 | 
			
		||||
                                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Outdated Sites" lineBreakMode="tailTruncation" minimumFontSize="10" id="ga8-ha-4zb">
 | 
			
		||||
                                        <rect key="frame" x="70" y="20" width="210" height="21"/>
 | 
			
		||||
                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
 | 
			
		||||
                                        <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
 | 
			
		||||
                                        <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
 | 
			
		||||
                                        <nil key="highlightedColor"/>
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" id="HPZ-qz-fpL">
 | 
			
		||||
                                        <rect key="frame" x="20" y="60" width="260" height="130"/>
 | 
			
		||||
                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
 | 
			
		||||
                                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
 | 
			
		||||
                                        <string key="text">Some of your sites have outdated passwords.  Tap this alert to see them.
 | 
			
		||||
 | 
			
		||||
You should upgrade these sites and update their account's passwords as soon as convenient.</string>
 | 
			
		||||
                                        <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
 | 
			
		||||
                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
 | 
			
		||||
                                        <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
 | 
			
		||||
                                    </textView>
 | 
			
		||||
                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Mtz-t0-jov">
 | 
			
		||||
                                        <rect key="frame" x="256" y="0.0" width="44" height="44"/>
 | 
			
		||||
                                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
 | 
			
		||||
                                        <accessibility key="accessibilityConfiguration" hint="" label="Close"/>
 | 
			
		||||
                                        <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
 | 
			
		||||
                                        <state key="normal" image="icon_cancel.png">
 | 
			
		||||
                                            <color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
 | 
			
		||||
                                            <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
 | 
			
		||||
                                        </state>
 | 
			
		||||
                                        <state key="highlighted">
 | 
			
		||||
                                            <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
 | 
			
		||||
                                        </state>
 | 
			
		||||
                                        <connections>
 | 
			
		||||
                                            <action selector="closeOutdatedAlert" destination="PQa-Xl-A3x" eventType="touchUpInside" id="f9T-Ij-qXx"/>
 | 
			
		||||
                                        </connections>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="pjE-9n-Zaz">
 | 
			
		||||
                                        <rect key="frame" x="212" y="0.0" width="44" height="44"/>
 | 
			
		||||
                                        <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
 | 
			
		||||
                                        <accessibility key="accessibilityConfiguration" hint="" label="Close"/>
 | 
			
		||||
                                        <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
 | 
			
		||||
                                        <state key="normal" image="icon_info.png">
 | 
			
		||||
                                            <color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
 | 
			
		||||
                                            <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
 | 
			
		||||
                                        </state>
 | 
			
		||||
                                        <state key="highlighted">
 | 
			
		||||
                                            <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
 | 
			
		||||
                                        </state>
 | 
			
		||||
                                        <connections>
 | 
			
		||||
                                            <action selector="infoOutdatedAlert" destination="PQa-Xl-A3x" eventType="touchUpInside" id="U2G-GA-wT2"/>
 | 
			
		||||
                                        </connections>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </subviews>
 | 
			
		||||
                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
 | 
			
		||||
                            </view>
 | 
			
		||||
                            <searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Site name" showsSearchResultsButton="YES" id="qeo-n2-WVh">
 | 
			
		||||
                                <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
 | 
			
		||||
                                <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
 | 
			
		||||
                                <gestureRecognizers/>
 | 
			
		||||
                                <textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
 | 
			
		||||
                                <scopeButtonTitles>
 | 
			
		||||
                                    <string>All</string>
 | 
			
		||||
                                    <string>Outdated</string>
 | 
			
		||||
                                </scopeButtonTitles>
 | 
			
		||||
                            </searchBar>
 | 
			
		||||
                            <view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL" userLabel="View - Search Tip">
 | 
			
		||||
                                <rect key="frame" x="10" y="15" width="300" height="60"/>
 | 
			
		||||
@@ -812,6 +878,9 @@ L4m3P4sSw0rD</string>
 | 
			
		||||
                        <outlet property="displayContainer" destination="g9q-4d-ZgJ" id="DC7-mb-msa"/>
 | 
			
		||||
                        <outlet property="helpContainer" destination="61G-By-qLB" id="Jwt-0d-ZAV"/>
 | 
			
		||||
                        <outlet property="helpView" destination="8FQ-x4-lR9" id="MOg-5s-kvK"/>
 | 
			
		||||
                        <outlet property="outdatedAlertBack" destination="f30-i7-VBv" id="2sX-9a-T9F"/>
 | 
			
		||||
                        <outlet property="outdatedAlertCloseButton" destination="Mtz-t0-jov" id="gjS-mo-wcQ"/>
 | 
			
		||||
                        <outlet property="outdatedAlertContainer" destination="6wk-NU-VQG" id="f48-bw-Ucx"/>
 | 
			
		||||
                        <outlet property="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
 | 
			
		||||
                        <outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
 | 
			
		||||
                        <outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/>
 | 
			
		||||
@@ -1553,6 +1622,7 @@ Pink fluffy door frame.</string>
 | 
			
		||||
        <image name="icon_action.png" width="32" height="32"/>
 | 
			
		||||
        <image name="icon_cancel.png" width="32" height="32"/>
 | 
			
		||||
        <image name="icon_edit.png" width="32" height="32"/>
 | 
			
		||||
        <image name="icon_info.png" width="32" height="32"/>
 | 
			
		||||
        <image name="icon_person.png" width="32" height="32"/>
 | 
			
		||||
        <image name="icon_plus.png" width="32" height="32"/>
 | 
			
		||||
        <image name="icon_up.png" width="32" height="32"/>
 | 
			
		||||
@@ -1585,10 +1655,12 @@ Pink fluffy door frame.</string>
 | 
			
		||||
            <relationships>
 | 
			
		||||
                <relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
 | 
			
		||||
                <relationship kind="action" name="closeAlert"/>
 | 
			
		||||
                <relationship kind="action" name="closeOutdatedAlert"/>
 | 
			
		||||
                <relationship kind="action" name="copyContent"/>
 | 
			
		||||
                <relationship kind="action" name="editPassword"/>
 | 
			
		||||
                <relationship kind="action" name="editUserName:" candidateClass="UILongPressGestureRecognizer"/>
 | 
			
		||||
                <relationship kind="action" name="incrementPasswordCounter"/>
 | 
			
		||||
                <relationship kind="action" name="infoOutdatedAlert"/>
 | 
			
		||||
                <relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
 | 
			
		||||
                <relationship kind="action" name="toggleUser"/>
 | 
			
		||||
                <relationship kind="action" name="upgradePassword"/>
 | 
			
		||||
@@ -1603,6 +1675,9 @@ Pink fluffy door frame.</string>
 | 
			
		||||
                <relationship kind="outlet" name="displayContainer" candidateClass="UIView"/>
 | 
			
		||||
                <relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
 | 
			
		||||
                <relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
 | 
			
		||||
                <relationship kind="outlet" name="outdatedAlertBack" candidateClass="UIImageView"/>
 | 
			
		||||
                <relationship kind="outlet" name="outdatedAlertCloseButton" candidateClass="UIButton"/>
 | 
			
		||||
                <relationship kind="outlet" name="outdatedAlertContainer" candidateClass="UIView"/>
 | 
			
		||||
                <relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
 | 
			
		||||
                <relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
 | 
			
		||||
                <relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
                $(".Class").css("display", "none");
 | 
			
		||||
                if (!$(".Class." + activeClass).length)
 | 
			
		||||
                    return "Not found: " + activeClass;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                $(".Class." + activeClass).css("display", "block");
 | 
			
		||||
            }
 | 
			
		||||
        </script>
 | 
			
		||||
@@ -75,13 +75,13 @@
 | 
			
		||||
            <b>While searching</b>, the names of previously used sites will be listed.<br />
 | 
			
		||||
            Tap one of these results to go straight to its password.
 | 
			
		||||
        </p>
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <h2 id="2">— 2 —</h2>
 | 
			
		||||
        <p>
 | 
			
		||||
            <b>The site</b>'s password is now displayed.<br />
 | 
			
		||||
            Tap it to <i>copy the password</i>.  Once copied, you can switch to another application and paste it into a password field.
 | 
			
		||||
        </p>
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <p class="Class MPElementStoredEntity">
 | 
			
		||||
            <b>To change</b> the password for this site, tap the <i>edit icon</i> <img src="icon_edit.png" />.
 | 
			
		||||
        </p>
 | 
			
		||||
@@ -90,7 +90,7 @@
 | 
			
		||||
            <b>Below the password</b> you can set the <i>password type</i>.  Some types <i>create a password for you</i>,
 | 
			
		||||
            others let you <i>choose your own</i>.
 | 
			
		||||
        </p>
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <p class="Class MPElementGeneratedEntity">
 | 
			
		||||
            <b>If the site complains</b> when you try to set or update the password, try changing the password type.
 | 
			
		||||
        </p>
 | 
			
		||||
@@ -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.
 | 
			
		||||
        </p>
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <h3 id="custom">I can't change all my passwords.<br />
 | 
			
		||||
            Some of them were assigned to me.</h3>
 | 
			
		||||
        <p>
 | 
			
		||||
@@ -295,6 +295,34 @@
 | 
			
		||||
            I invite anyone with a technical background to go through these resources to make certain of the trustworthiness of Master Password.
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
        <h3 id="outdated">Is the algorithm stable?<br />
 | 
			
		||||
            Will my passwords ever change?</h3>
 | 
			
		||||
        <p>
 | 
			
		||||
            While we're very confident of the strength of the Master Password algorithm, we're also constantly keeping an eye out
 | 
			
		||||
            for what the evolutions are of hackers' tools and capabilities.  To give you the best possible protection, there is
 | 
			
		||||
            always the possibility that we'll have to make tweaks to the Master Password algorithm in order to fend off any
 | 
			
		||||
            attempts at breaking in.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            Usually, these tweaks will be automatically applied when you install the latest version.  In this case, you will notice
 | 
			
		||||
            nothing and all you need to take away from this is that it's best to always be running the latest version of Master Password.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            It is possible, however, that to apply an upgrade to your passwords, a new password will need to be set for your site's
 | 
			
		||||
            account.  In this case, Master Password will leave your passwords the way they are but give you the <em>option</em> of
 | 
			
		||||
            upgrading your passwords when it's convenient to you.  Whenever you're ready, just tap the upgrade password icon and
 | 
			
		||||
            Master Password will show you the old password and the new one so that you can easily update your site's account.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            <em>Please note</em>: if Master Password warns you that you have outdated passwords, it's best to upgrade them all
 | 
			
		||||
            as soon as convenient.  If you lose your device or data and recreate your Master Password user on another device,
 | 
			
		||||
            Master Password can only regenerate the passwords for you that you've upgraded.  iCloud/iTunes sync or exports are not
 | 
			
		||||
            affected, so these are good ways to safely back up your passwords.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            <a href="?outdated">Tap here</a> to check if you have any outdated passwords.
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
        <h3 id="branded">This stuff is gold.<br />
 | 
			
		||||
            I want one branded for our company.</h3>
 | 
			
		||||
        <p>
 | 
			
		||||
@@ -304,7 +332,7 @@
 | 
			
		||||
        <p>
 | 
			
		||||
            Master Password can also be used as a One-Time Password token generator to secure your infrastructure and client access.
 | 
			
		||||
        </p>
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        <footer>
 | 
			
		||||
            <a href="http://masterpassword.lyndir.com">Homepage</a> | <a href="http://www.lyndir.com">Lyndir</a> |
 | 
			
		||||
            <a href="http://www.lyndir.com/contact">Contact</a>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user