2
0

Better abstraction for key & algorithm + V1

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

View File

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

2
External/Pearl vendored

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

View File

@ -7,13 +7,20 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 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 */; }; DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.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 */; }; DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; }; DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAC6326D148680650075AEA5 /* 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 */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference 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; }; 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; }; 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; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = "<group>"; };
@ -1999,17 +2009,25 @@
DA5BFA50147E415C00F98B1E /* MasterPassword */ = { DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */, 93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */,
DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */, 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */,
DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */, 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */,
DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */, 93D39C68AFA48A13015E4FAC /* MPKey.h */,
DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */, 93D39E81EFABC6085AC8AE69 /* MPKey.m */,
DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */, 93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */,
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */,
93D398E394E311C545E0A057 /* MPAlgorithm.h */,
DA0E07941577FE490008A67E /* MPEntities.h */, DA0E07941577FE490008A67E /* MPEntities.h */,
DA0E07951577FE490008A67E /* MPEntities.m */, DA0E07951577FE490008A67E /* MPEntities.m */,
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */, DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DA0757E815B3681200613FAA /* MPElementEntity.h */, DA0F9F3115B55397007ED9BC /* MPUserEntity.h */,
DA0757E915B3681200613FAA /* MPElementEntity.m */, 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 */, DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */, DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */, DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@ -2019,7 +2037,6 @@
DA600C2615056427008E9AB6 /* MPConfig.h */, DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */, DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */, DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
DAB8D43E15036BCF00CED3BC /* iOS */, DAB8D43E15036BCF00CED3BC /* iOS */,
); );
path = MasterPassword; path = MasterPassword;
@ -4238,17 +4255,20 @@
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */, DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */, DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */, DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */, DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */, DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */, DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */, DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
DA0E07961577FE490008A67E /* MPEntities.m in Sources */, DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */, DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */, 93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */,
DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */, 93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */,
DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */, 93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */,
DA0757EA15B3681200613FAA /* MPElementEntity.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; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -0,0 +1,44 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithm
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPKey.h"
#import "MPElementGeneratedEntity.h"
#define MPAlgorithmDefaultVersion 1
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
@protocol MPAlgorithm<NSObject>
@required
- (NSUInteger)version;
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
- (MPKey *)keyFromKeyData:(NSData *)keyData;
- (NSData *)keyIDForKeyData:(NSData *)keyData;
- (NSString *)nameOfType:(MPElementType)type;
- (NSString *)shortNameOfType:(MPElementType)type;
- (NSString *)classNameOfType:(MPElementType)type;
- (Class)classOfType:(MPElementType)type;
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
@end
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);

View File

@ -0,0 +1,42 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithm
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
#import "MPEntities.h"
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
static NSMutableDictionary *versionToAlgorithm = nil;
if (!versionToAlgorithm)
versionToAlgorithm = [NSMutableDictionary dictionary];
id<MPAlgorithm> algorithm = [versionToAlgorithm objectForKey:PearlUnsignedInteger(version)];
if (!algorithm)
if ((algorithm = [NSClassFromString(PearlString(@"MPAlgorithmV%u", version)) new]))
[versionToAlgorithm setObject:algorithm forKey:PearlUnsignedInteger(version)];
return algorithm;
}
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
if (PearlCFBundleVersionCompare(bundleVersion, @"1.3") == NSOrderedAscending)
// Pre-1.3
return MPAlgorithmForVersion(0);
return MPAlgorithmDefault;
}

View File

@ -0,0 +1,21 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV0
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithm.h"
@interface MPAlgorithmV0 : NSObject <MPAlgorithm>
@end

View File

@ -1,54 +1,83 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
// //
// MPTypes.m // MPAlgorithmV0
// MasterPassword
// //
// Created by Maarten Billemont on 02/01/12. // Created by Maarten Billemont on 16/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved. // Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
// //
#import "MPAlgorithmV0.h"
#import "MPEntities.h" #import "MPEntities.h"
#define MP_N 32768 #define MP_N 32768
#define MP_r 8 #define MP_r 8
#define MP_p 2 #define MP_p 2
#define MP_dkLen 64 #define MP_dkLen 64
#define MP_hash PearlHashSHA256 #define MP_hash PearlHashSHA256
NSData *keyForPassword(NSString *password, NSString *username) { @implementation MPAlgorithmV0
uint32_t nusernameLength = htonl(username.length); - (NSUInteger)version {
NSDate *start = [NSDate date];
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding] return 0;
usingSalt:[NSData dataByConcatenatingDatas: }
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nusernameLength - (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
length:sizeof(nusernameLength)],
[username dataUsingEncoding:NSUTF8StringEncoding], if (element.version != [self version] - 1)
nil] N:MP_N r:MP_r p:MP_p]; // 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; return key;
} }
- (MPKey *)keyFromKeyData:(NSData *)keyData {
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) { return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
} }
- (NSData *)keyIDForKeyData:(NSData *)keyData {
NSData *keyIDForPassword(NSString *password, NSString *username) { return [keyData hashWith:MP_hash];
return keyIDForKey(keyForPassword(password, username));
} }
NSData *keyIDForKey(NSData *key) { - (NSString *)nameOfType:(MPElementType)type {
return [key hashWith:MP_hash];
}
NSString *NSStringFromMPElementType(MPElementType type) {
if (!type) if (!type)
return nil; return nil;
@ -77,13 +106,12 @@ NSString *NSStringFromMPElementType(MPElementType type) {
case MPElementTypeStoredDevicePrivate: case MPElementTypeStoredDevicePrivate:
return @"Device Private Password"; 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) if (!type)
return nil; return nil;
@ -112,13 +140,17 @@ NSString *NSStringShortFromMPElementType(MPElementType type) {
case MPElementTypeStoredDevicePrivate: case MPElementTypeStoredDevicePrivate:
return @"Device"; 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) if (!type)
return nil; return nil;
@ -147,63 +179,56 @@ Class ClassFromMPElementType(MPElementType type) {
case MPElementTypeStoredDevicePrivate: case MPElementTypeStoredDevicePrivate:
return [MPElementStoredEntity class]; 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 (!(element.type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", [self nameOfType:element.type], element.name);
if (!(type & MPElementTypeClassGenerated)) {
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil; return nil;
} }
if (!name.length) { if (!element.name.length) {
err(@"Missing name."); err(@"Missing name.");
return nil; return nil;
} }
if (!key.length) { if (!key.keyData.length) {
err(@"Missing key."); err(@"Missing key.");
return nil; 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) if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]]; withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password // Determine the seed whose bytes will be used for calculating a password
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length); uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)]; NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)]; 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: NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
nameLengthBytes, nameLengthBytes,
[name dataUsingEncoding:NSUTF8StringEncoding], [element.name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes, counterBytes,
nil] nil]
hmacWith:PearlHashSHA256 key:key]; hmacWith:PearlHashSHA256 key:key.keyData];
trc(@"seed is: %@", [seed encodeBase64]); trc(@"seed is: %@", [seed encodeBase64]);
const char *seedBytes = seed.bytes; const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte. // Determine the cipher from the first seed byte.
assert([seed length]); assert([seed length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)] NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
valueForKey:NSStringFromMPElementType(type)]; valueForKey:[self nameOfType:element.type]];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; 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. // Encode the content, character by character, using subsequent seed bytes and the cipher.
assert([seed length] >= [cipher length] + 1); assert([seed length] >= [cipher length] + 1);
@ -222,10 +247,4 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
return content; return content;
} }
void MPElementMigrate(MPElementEntity *element, BOOL explicit) { @end
if (element.version == 0 && explicit) {
// 0 -> 1
element.version = 1;
}
}

View File

@ -0,0 +1,22 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV0.h"
@interface MPAlgorithmV1 : MPAlgorithmV0
@end

View File

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

View File

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

View File

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

View File

@ -67,11 +67,11 @@
return storeManager; return storeManager;
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel] storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"] localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared" containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSPersistentStoreFileProtectionKey] forKey:NSPersistentStoreFileProtectionKey]
#else #else
additionalStoreOptions:nil additionalStoreOptions:nil
#endif #endif
@ -83,9 +83,9 @@
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication] queue:nil object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[storeManager checkiCloudStatus]; [storeManager checkiCloudStatus];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
object:[NSApplication sharedApplication] queue:nil object:[NSApplication sharedApplication] queue:nil
@ -96,9 +96,9 @@
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
object:[UIApplication sharedApplication] queue:nil object:[UIApplication sharedApplication] queue:nil
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self saveContext]; [self saveContext];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication] queue:nil object:[NSApplication sharedApplication] queue:nil
@ -142,7 +142,8 @@
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled]; [TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud [[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]; [MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
} }
@ -168,13 +169,11 @@
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible]; [TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible attributes:nil];
attributes:nil];
manager.hardResetEnabled = YES; manager.hardResetEnabled = YES;
[manager hardResetLocalStorage]; [manager hardResetLocalStorage];
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."]; Throw(@"Local store was reset, application must be restarted to use it.");
return;
} }
case UbiquityStoreManagerErrorCauseOpenCloudStore: { case UbiquityStoreManagerErrorCauseOpenCloudStore: {
wrn(@"iCloud store could not be opened, resetting it."); wrn(@"iCloud store could not be opened, resetting it.");
@ -182,8 +181,7 @@
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible]; [TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible attributes:nil];
attributes:nil];
manager.hardResetEnabled = YES; manager.hardResetEnabled = YES;
[manager hardResetCloudStorage]; [manager hardResetCloudStorage];
break; break;
@ -209,7 +207,7 @@
} }
if (!sitePattern) { if (!sitePattern) {
sitePattern = [[NSRegularExpression alloc] 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]; options:0 error:&error];
if (error) if (error)
err(@"Error loading the site pattern: %@", error); err(@"Error loading the site pattern: %@", error);
@ -217,10 +215,10 @@
if (!headerPattern || !sitePattern) if (!headerPattern || !sitePattern)
return MPImportResultInternalError; return MPImportResultInternalError;
NSData *key = nil; MPKey *key = nil;
NSString *keyIDHex = nil, *userName = nil; MPUserEntity *user = nil;
MPUserEntity *user = nil; NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO; BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set]; NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
@ -247,20 +245,19 @@
} }
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject]; range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) { if ([headerName isEqualToString:@"User Name"]) {
userName = headerValue; userName = headerValue;
key = keyForPassword(password, userName);
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
} }
if ([headerName isEqualToString:@"Key ID"]) { if ([headerName isEqualToString:@"Key ID"])
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]]) keyIDHex = headerValue;
return MPImportResultInvalidPassword; if ([headerName isEqualToString:@"Version"])
} bundleVersion = headerValue;
if ([headerName isEqualToString:@"Passwords"]) { if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"]) if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES; clearText = YES;
@ -272,6 +269,9 @@
continue; continue;
if (!keyIDHex || ![userName length]) if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
return MPImportResultInvalidPassword;
if (![importedSiteLine length]) if (![importedSiteLine length])
continue; continue;
@ -285,8 +285,9 @@
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
// Find existing site. // Find existing site.
if (user) { if (user) {
@ -300,7 +301,7 @@
} }
[elementsToDelete addObjectsFromArray:existingSites]; [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. // Import new sites.
if (!user) { if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
user.name = userName; user.name = userName;
user.keyID = [keyIDHex decodeHex]; user.keyID = [keyIDHex decodeHex];
} }
for (NSArray *siteElements in importedSiteElements) { for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]]; NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue]; NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue]; MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSString *name = [siteElements objectAtIndex:3]; NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue];
NSString *exportContent = [siteElements objectAtIndex:4]; NSString *name = [siteElements objectAtIndex:4];
NSString *exportContent = [siteElements objectAtIndex:5];
// Create new site. // Create new site.
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type) MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
element.name = name; element.name = name;
element.user = user; element.user = user;
element.type = type; element.type = type;
element.uses = uses; element.uses = uses;
element.lastUsed = lastUsed; element.lastUsed = lastUsed;
element.version = version;
if ([exportContent length]) { if ([exportContent length]) {
if (clearText) if (clearText)
[element importClearTextContent:exportContent usingKey:key]; [element importClearTextContent:exportContent usingKey:key];
@ -355,8 +358,7 @@
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported]; [TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil];
attributes:nil];
return MPImportResultSuccess; return MPImportResultSuccess;
} }
@ -405,7 +407,8 @@
} }
[export appendFormat:@"%@ %8d %8d %20s\t%@\n", [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: @""]; ? content: @""];
} }

View File

@ -2,7 +2,7 @@
// MPElementEntity.h // MPElementEntity.h
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@ -18,9 +18,9 @@
@property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_; @property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSNumber * type_; @property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSString * userName;
@property (nonatomic, retain) NSNumber * uses_; @property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_; @property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSString * userName;
@property (nonatomic, retain) MPUserEntity *user; @property (nonatomic, retain) MPUserEntity *user;
@end @end

View File

@ -2,7 +2,7 @@
// MPElementEntity.m // MPElementEntity.m
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@ -17,9 +17,9 @@
@dynamic name; @dynamic name;
@dynamic requiresExplicitMigration_; @dynamic requiresExplicitMigration_;
@dynamic type_; @dynamic type_;
@dynamic userName;
@dynamic uses_; @dynamic uses_;
@dynamic version_; @dynamic version_;
@dynamic userName;
@dynamic user; @dynamic user;
@end @end

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.h // MPElementGeneratedEntity.h
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.m // MPElementGeneratedEntity.m
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.h // MPElementStoredEntity.h
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.m // MPElementStoredEntity.m
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //

View File

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

View File

@ -8,8 +8,6 @@
#import "MPEntities.h" #import "MPEntities.h"
#import "MPAppDelegate.h" #import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
#import "MPUserEntity.h"
@implementation MPElementEntity (MP) @implementation MPElementEntity (MP)
@ -23,6 +21,26 @@
self.type_ = PearlUnsignedInteger(aType); 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 { - (NSUInteger)uses {
return [self.uses_ unsignedIntegerValue]; return [self.uses_ unsignedIntegerValue];
@ -53,6 +71,11 @@
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration); self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
} }
- (id<MPAlgorithm>)algorithm {
return MPAlgorithmForVersion(self.version);
}
- (NSUInteger)use { - (NSUInteger)use {
self.lastUsed = [NSDate date]; self.lastUsed = [NSDate date];
@ -73,8 +96,8 @@
} }
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key { - (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
} }
- (NSString *)description { - (NSString *)description {
@ -84,8 +107,22 @@
- (NSString *)debugDescription { - (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, 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); 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 @end
@ -103,8 +140,8 @@
} }
- (id)content { - (id)content {
NSData *key = [MPAppDelegate get].key; MPKey *key = [MPAppDelegate get].key;
if (!key) if (!key)
return nil; return nil;
@ -116,7 +153,7 @@
if (![self.name length]) if (![self.name length])
return nil; return nil;
return MPCalculateContent(self.type, self.name, key, self.counter); return [self.algorithm generateContentForElement:self usingKey:key];
} }
@end @end
@ -130,12 +167,12 @@
@"DevicePrivate", (__bridge id)kSecAttrService, @"DevicePrivate", (__bridge id)kSecAttrService,
name, (__bridge id)kSecAttrAccount, name, (__bridge id)kSecAttrAccount,
nil] nil]
matches:nil]; matches:nil];
} }
- (id)content { - (id)content {
NSData *key = [MPAppDelegate get].key; MPKey *key = [MPAppDelegate get].key;
if (!key) if (!key)
return nil; return nil;
@ -143,18 +180,18 @@
} }
- (void)setContent:(id)content { - (void)setContent:(id)content {
NSData *key = [MPAppDelegate get].key; MPKey *key = [MPAppDelegate get].key;
if (!key) if (!key)
return; return;
[self setContent:content usingKey:key]; [self setContent:content usingKey:key];
} }
- (id)contentUsingKey:(NSData *)key { - (id)contentUsingKey:(MPKey *)key {
assert(self.type & MPElementTypeClassStored); assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]); assert([key.keyID isEqualToData:self.user.keyID]);
NSData *encryptedContent; NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate) if (self.type & MPElementFeatureDevicePrivate)
@ -162,25 +199,29 @@
else else
encryptedContent = self.contentObject; 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]; 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(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) { if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name] [PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
encryptedContent, (__bridge id)kSecValueData, encryptedContent, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible, (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
#endif (__bridge id)kSecAttrAccessible,
nil]]; #endif
nil]];
self.contentObject = nil; self.contentObject = nil;
} else } else
self.contentObject = encryptedContent; self.contentObject = encryptedContent;
@ -196,8 +237,8 @@
self.contentObject = [protectedContent decodeBase64]; self.contentObject = [protectedContent decodeBase64];
} }
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key { - (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
[self setContent:clearContent usingKey:key]; [self setContent:clearContent usingKey:key];
} }
@ -230,17 +271,26 @@
return (MPElementType)[self.defaultType_ unsignedIntegerValue]; return (MPElementType)[self.defaultType_ unsignedIntegerValue];
} }
- (NSString *)userID {
return [MPUserEntity idFor:self.name];
}
- (void)setDefaultType:(MPElementType)aDefaultType { - (void)setDefaultType:(MPElementType)aDefaultType {
self.defaultType_ = PearlUnsignedInteger(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 { + (NSString *)idFor:(NSString *)userName {
return [[userName hashWith:PearlHashSHA1] encodeHex]; return [[userName hashWith:PearlHashSHA1] encodeHex];

33
MasterPassword/MPKey.h Normal file
View File

@ -0,0 +1,33 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPKey
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MPAlgorithm;
@interface MPKey : NSObject
@property (nonatomic, readonly, strong) id<MPAlgorithm> algorithm;
@property (nonatomic, readonly, strong) NSData *keyData;
@property (nonatomic, readonly, strong) NSData *keyID;
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm;
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength;
- (BOOL)isEqualToKey:(MPKey *)key;
@end

66
MasterPassword/MPKey.m Normal file
View File

@ -0,0 +1,66 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPKey
//
// Created by Maarten Billemont on 16/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPKey.h"
#import "MPAlgorithm.h"
@interface MPKey ()
@property (nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
@property (nonatomic, readwrite, strong) NSData *keyData;
@property (nonatomic, readwrite, strong) NSData *keyID;
@end
@implementation MPKey
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
if (!(self = [super init]))
return nil;
self.keyData = keyData;
self.algorithm = algorithm;
self.keyID = [self.algorithm keyIDForKeyData:keyData];
return self;
}
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange(0, MIN(subKeyLength, self.keyData.length))];
return [self.algorithm keyFromKeyData:subKeyData];
}
- (BOOL)isEqualToKey:(MPKey *)key {
return [self.keyID isEqualToData:key.keyID];
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[MPKey class]])
return NO;
return [self isEqualToKey:object];
}
@end

View File

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

View File

@ -2,7 +2,7 @@
// MPUserEntity.h // MPUserEntity.h
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@ -19,6 +19,7 @@
@property (nonatomic, retain) NSDate * lastUsed; @property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_; @property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSSet *elements; @property (nonatomic, retain) NSSet *elements;
@end @end

View File

@ -2,7 +2,7 @@
// MPUserEntity.m // MPUserEntity.m
// MasterPassword-iOS // 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. // Copyright (c) 2012 Lyndir. All rights reserved.
// //
@ -18,6 +18,7 @@
@dynamic lastUsed; @dynamic lastUsed;
@dynamic name; @dynamic name;
@dynamic saveKey_; @dynamic saveKey_;
@dynamic requiresExplicitMigration_;
@dynamic elements; @dynamic elements;
@end @end

View File

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

View File

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

View File

@ -13,8 +13,6 @@
#import "LocalyticsSession.h" #import "LocalyticsSession.h"
void MPElementMigrate(MPElementEntity *entity, BOOL i);
@implementation MPMainViewController @implementation MPMainViewController
@synthesize userNameHidden = _userNameHidden; @synthesize userNameHidden = _userNameHidden;
@synthesize activeElement = _activeElement; @synthesize activeElement = _activeElement;
@ -45,6 +43,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
@synthesize userNameContainer = _userNameContainer; @synthesize userNameContainer = _userNameContainer;
@synthesize userNameField = _userNameField; @synthesize userNameField = _userNameField;
@synthesize passwordUser = _passwordUser; @synthesize passwordUser = _passwordUser;
@synthesize outdatedAlertContainer = _outdatedAlertContainer;
@synthesize outdatedAlertBack = _outdatedAlertBack;
@synthesize outdatedAlertCloseButton = _outdatedAlertCloseButton;
@synthesize contentField = _contentField; @synthesize contentField = _contentField;
@synthesize contentTipCleanup = _contentTipCleanup, toolTipCleanup = _toolTipCleanup; @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.passwordIncrementer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resetPasswordCounter:)]];
[self.userNameContainer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(editUserName:)]]; [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"]]; self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
@ -90,13 +92,17 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.alertBody.text = nil; self.alertBody.text = nil;
self.toolTipEditIcon.hidden = YES; 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 [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationElementUpdated object:nil queue:nil
usingBlock:^void(NSNotification *note) { usingBlock:^void(NSNotification *note) {
if (self.activeElement.type & MPElementTypeClassStored if (self.activeElement.type & MPElementTypeClassStored
&& ![[self.activeElement.content description] length]) && ![[self.activeElement.content description] length])
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon]; [self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
if (self.activeElement.requiresExplicitMigration) 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 [[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^void(NSNotification *note) { usingBlock:^void(NSNotification *note) {
@ -118,14 +124,41 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.activeElement = nil; self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil; self.searchDisplayController.searchBar.text = nil;
self.alertContainer.alpha = 0; self.alertContainer.alpha = 0;
self.searchTipContainer.alpha = 0; self.outdatedAlertContainer.alpha = 0;
self.actionsTipContainer.alpha = 0; self.searchTipContainer.alpha = 0;
self.typeTipContainer.alpha = 0; self.actionsTipContainer.alpha = 0;
self.toolTipContainer.alpha = 0; self.typeTipContainer.alpha = 0;
self.toolTipContainer.alpha = 0;
[self updateAnimated:animated]; [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]; [super viewWillAppear:animated];
} }
@ -189,6 +222,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[self setUserNameTipBody:nil]; [self setUserNameTipBody:nil];
[self setUserNameContainer:nil]; [self setUserNameContainer:nil];
[self setPasswordUser:nil]; [self setPasswordUser:nil];
[self setOutdatedAlertContainer:nil];
[self setOutdatedAlertCloseButton:nil];
[self setOutdatedAlertBack:nil];
[super viewDidUnload]; [super viewDidUnload];
} }
@ -208,7 +244,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.passwordIncrementer.alpha = 0; self.passwordIncrementer.alpha = 0;
self.passwordEdit.alpha = 0; self.passwordEdit.alpha = 0;
self.passwordUpgrade.alpha = 0; self.passwordUpgrade.alpha = 0;
self.passwordUser.alpha = 0; self.passwordUser.alpha = 0;
if (self.activeElement) if (self.activeElement)
self.passwordUser.alpha = 0.5f; self.passwordUser.alpha = 0.5f;
@ -224,18 +260,18 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
if (self.activeElement.type & MPElementTypeClassStored) if (self.activeElement.type & MPElementTypeClassStored)
self.passwordEdit.alpha = 0.5f; self.passwordEdit.alpha = 0.5f;
} }
self.siteName.text = self.activeElement.name; 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]; forState:UIControlStateNormal];
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]]) if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter); self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter);
self.contentField.enabled = NO; self.contentField.enabled = NO;
self.contentField.text = @""; self.contentField.text = @"";
if (self.activeElement.name && ![self.activeElement isDeleted]) if (self.activeElement.name && ![self.activeElement isDeleted])
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [self.activeElement.content description]; NSString *description = [self.activeElement.content description];
@ -246,8 +282,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
}); });
self.userNameField.enabled = NO; self.userNameField.enabled = NO;
self.userNameField.text = self.activeElement.userName; self.userNameField.text = self.activeElement.userName;
self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName == nil)); self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName
== nil));
[self updateUserHiddenAnimated:NO]; [self updateUserHiddenAnimated:NO];
} }
@ -263,20 +300,20 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
} }
- (void)updateHelpHiddenAnimated:(BOOL)animated { - (void)updateHelpHiddenAnimated:(BOOL)animated {
if (animated) { if (animated) {
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
[self updateHelpHiddenAnimated:NO]; [self updateHelpHiddenAnimated:NO];
}]; }];
return; return;
} }
if ([[MPiOSConfig get].helpHidden boolValue]) { if ([[MPiOSConfig get].helpHidden boolValue]) {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44); 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.helpContainer.frame = CGRectSetY(self.helpContainer.frame, self.view.bounds.size.height);
} else { } else {
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225); self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225);
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266); self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266);
} }
} }
@ -302,9 +339,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
} }
if (self.userNameHidden) { if (self.userNameHidden) {
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87); self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87);
} else { } 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 { - (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString: NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))]; PearlString(@"setClass('%@');", self.activeElement.typeClassName)];
if (error.length) if (error.length)
err(@"helpView.setClass: %@", error); err(@"helpView.setClass: %@", error);
} }
@ -436,13 +473,13 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointCopyToPasteboard]; [TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type", self.activeElement.typeName, @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
nil]]; nil]];
} }
- (IBAction)copyUserName { - (IBAction)copyUserName:(UITapGestureRecognizer *)sender {
if (!self.activeElement.userName) if (!self.activeElement.userName)
return; return;
@ -455,7 +492,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointCopyUserNameToPasteboard]; [TestFlight passCheckpoint:MPCheckpointCopyUserNameToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyUserNameToPasteboard [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyUserNameToPasteboard
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type", self.activeElement.typeName, @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
nil]]; nil]];
@ -479,8 +516,8 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter]; [TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType( self.activeElement.typeName,
self.activeElement.type), @"type", @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
nil]]; nil]];
@ -510,8 +547,8 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter]; [TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType( self.activeElement.typeName,
self.activeElement.type), @"type", @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
nil]]; nil]];
@ -523,7 +560,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
if (sender.state != UIGestureRecognizerStateBegan) if (sender.state != UIGestureRecognizerStateBegan)
// Only fire when the gesture was first detected. // Only fire when the gesture was first detected.
return; return;
if (!self.activeElement) if (!self.activeElement)
return; return;
@ -532,8 +569,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointEditUserName]; [TestFlight passCheckpoint:MPCheckpointEditUserName];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditUserName attributes:[NSDictionary dictionaryWithObjectsAndKeys: [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditUserName attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType( self.activeElement.typeName,
self.activeElement.type),
@"type", @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
@ -580,8 +616,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointEditPassword]; [TestFlight passCheckpoint:MPCheckpointEditPassword];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType( self.activeElement.typeName, @"type",
self.activeElement.type), @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
nil]]; 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." @"This upgrade improves the site's compatibility with the latest version of Master Password."
do:^{ do:^{
inf(@"Explicitly migrating element: %@", self.activeElement); inf(@"Explicitly migrating element: %@", self.activeElement);
MPElementMigrate(self.activeElement, YES); [self.activeElement migrateExplicitly:YES];
[TestFlight passCheckpoint:MPCheckpointExplicitMigration]; [TestFlight passCheckpoint:MPCheckpointExplicitMigration];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration
attributes:[NSDictionary dictionaryWithObjectsAndKeys: attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType( self.activeElement.typeName,
self.activeElement.type), @"type", @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
nil]]; nil]];
}]; }];
} }
- (IBAction)searchOutdatedElements:(UITapGestureRecognizer *)sender {
self.searchDisplayController.searchBar.selectedScopeButtonIndex = MPSearchScopeOutdated;
self.searchDisplayController.searchBar.searchResultsButtonSelected = YES;
[self.searchDisplayController.searchBar becomeFirstResponder];
}
- (IBAction)closeAlert { - (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
@ -625,6 +667,23 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[TestFlight passCheckpoint:MPCheckpointCloseAlert]; [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 { - (IBAction)action:(id)sender {
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic [PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
@ -721,6 +780,11 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
[[MPAppDelegate get] signOutAnimated:YES]; [[MPAppDelegate get] signOutAnimated:YES];
break; break;
} }
default: {
wrn(@"Unsupported action: %u", buttonIndex - [sheet firstOtherButtonIndex]);
break;
}
} }
[TestFlight passCheckpoint:MPCheckpointAction]; [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." @"You will need to update your account's old password to the new one."
do:^{ do:^{
// Update password type. // 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. // Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{ [[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType( MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:[self.activeElement.algorithm classNameOfType:type]
type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name; newElement.name = self.activeElement.name;
newElement.user = self.activeElement.user; newElement.user = self.activeElement.user;
newElement.uses = self.activeElement.uses; newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed; newElement.lastUsed = self.activeElement.lastUsed;
newElement.version = self.activeElement.version;
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement]; [[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement; self.activeElement = newElement;
@ -813,10 +877,9 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.searchDisplayController.searchBar.text = self.activeElement.name; self.searchDisplayController.searchBar.text = self.activeElement.name;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated object:self.activeElement]; [[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: [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType( self.activeElement.typeName,
self.activeElement.type),
@"type", @"type",
PearlUnsignedInteger(self.activeElement.version), PearlUnsignedInteger(self.activeElement.version),
@"version", @"version",
@ -854,7 +917,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
} }
if (textField == self.userNameField) { if (textField == self.userNameField) {
self.userNameField.enabled = NO; self.userNameField.enabled = NO;
if (![[MPiOSConfig get].userNameTipShown boolValue]) { if (![[MPiOSConfig get].userNameTipShown boolValue]) {
[self showUserNameTip:@"Tap to copy or hold to edit."]; [self showUserNameTip:@"Tap to copy or hold to edit."];
[MPiOSConfig get].userNameTipShown = PearlBool(YES); [MPiOSConfig get].userNameTipShown = PearlBool(YES);
@ -864,7 +927,7 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
self.activeElement.userName = self.userNameField.text; self.activeElement.userName = self.userNameField.text;
else else
self.activeElement.userName = nil; self.activeElement.userName = nil;
[[MPAppDelegate get] saveContext]; [[MPAppDelegate get] saveContext];
} }
} }
@ -873,7 +936,11 @@ void MPElementMigrate(MPElementEntity *entity, BOOL i);
navigationType:(UIWebViewNavigationType)navigationType { navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) { if (navigationType == UIWebViewNavigationTypeLinkClicked) {
inf(@"External link: %@", [request URL]); if ([[[request URL] query] isEqualToString:@"outdated"]) {
[self searchOutdatedElements:nil];
return NO;
}
[TestFlight passCheckpoint:MPCheckpointExternalLink]; [TestFlight passCheckpoint:MPCheckpointExternalLink];
[[UIApplication sharedApplication] openURL:[request URL]]; [[UIApplication sharedApplication] openURL:[request URL]];

View File

@ -75,7 +75,7 @@
} recurse:NO]; } recurse:NO];
self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey; 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]; [super viewWillAppear:animated];
} }
@ -139,7 +139,7 @@
[MPAppDelegate get].activeUser.defaultType = type; [MPAppDelegate get].activeUser.defaultType = type;
[[MPAppDelegate get] saveContext]; [[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 { - (MPElementType)selectedType {

View File

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

View File

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

View File

@ -664,11 +664,77 @@ L4m3P4sSw0rD</string>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<gestureRecognizers/> <gestureRecognizers/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/> <textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
<scopeButtonTitles>
<string>All</string>
<string>Outdated</string>
</scopeButtonTitles>
</searchBar> </searchBar>
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL" userLabel="View - Search Tip"> <view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL" userLabel="View - Search Tip">
<rect key="frame" x="10" y="15" width="300" height="60"/> <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="displayContainer" destination="g9q-4d-ZgJ" id="DC7-mb-msa"/>
<outlet property="helpContainer" destination="61G-By-qLB" id="Jwt-0d-ZAV"/> <outlet property="helpContainer" destination="61G-By-qLB" id="Jwt-0d-ZAV"/>
<outlet property="helpView" destination="8FQ-x4-lR9" id="MOg-5s-kvK"/> <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="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
<outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/> <outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
<outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/> <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_action.png" width="32" height="32"/>
<image name="icon_cancel.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_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_person.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/> <image name="icon_plus.png" width="32" height="32"/>
<image name="icon_up.png" width="32" height="32"/> <image name="icon_up.png" width="32" height="32"/>
@ -1585,10 +1655,12 @@ Pink fluffy door frame.</string>
<relationships> <relationships>
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/> <relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="closeAlert"/> <relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="closeOutdatedAlert"/>
<relationship kind="action" name="copyContent"/> <relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editPassword"/> <relationship kind="action" name="editPassword"/>
<relationship kind="action" name="editUserName:" candidateClass="UILongPressGestureRecognizer"/> <relationship kind="action" name="editUserName:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="incrementPasswordCounter"/> <relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="infoOutdatedAlert"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/> <relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="toggleUser"/> <relationship kind="action" name="toggleUser"/>
<relationship kind="action" name="upgradePassword"/> <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="displayContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/> <relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/> <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="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/> <relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/> <relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>

View File

@ -55,7 +55,7 @@
$(".Class").css("display", "none"); $(".Class").css("display", "none");
if (!$(".Class." + activeClass).length) if (!$(".Class." + activeClass).length)
return "Not found: " + activeClass; return "Not found: " + activeClass;
$(".Class." + activeClass).css("display", "block"); $(".Class." + activeClass).css("display", "block");
} }
</script> </script>
@ -75,13 +75,13 @@
<b>While searching</b>, the names of previously used sites will be listed.<br /> <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. Tap one of these results to go straight to its password.
</p> </p>
<h2 id="2">&mdash; 2 &mdash;</h2> <h2 id="2">&mdash; 2 &mdash;</h2>
<p> <p>
<b>The site</b>'s password is now displayed.<br /> <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. 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>
<p class="Class MPElementStoredEntity"> <p class="Class MPElementStoredEntity">
<b>To change</b> the password for this site, tap the <i>edit icon</i> <img src="icon_edit.png" />. <b>To change</b> the password for this site, tap the <i>edit icon</i> <img src="icon_edit.png" />.
</p> </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>, <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>. others let you <i>choose your own</i>.
</p> </p>
<p class="Class MPElementGeneratedEntity"> <p class="Class MPElementGeneratedEntity">
<b>If the site complains</b> when you try to set or update the password, try changing the password type. <b>If the site complains</b> when you try to set or update the password, try changing the password type.
</p> </p>
@ -173,7 +173,7 @@
share the password with anyone else. Instead, the app creates secure passwords for use with whatever site 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. or purpose you might need a password for.
</p> </p>
<h3 id="custom">I can't change all my passwords.<br /> <h3 id="custom">I can't change all my passwords.<br />
Some of them were assigned to me.</h3> Some of them were assigned to me.</h3>
<p> <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. I invite anyone with a technical background to go through these resources to make certain of the trustworthiness of Master Password.
</p> </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 /> <h3 id="branded">This stuff is gold.<br />
I want one branded for our company.</h3> I want one branded for our company.</h3>
<p> <p>
@ -304,7 +332,7 @@
<p> <p>
Master Password can also be used as a One-Time Password token generator to secure your infrastructure and client access. Master Password can also be used as a One-Time Password token generator to secure your infrastructure and client access.
</p> </p>
<footer> <footer>
<a href="http://masterpassword.lyndir.com">Homepage</a> | <a href="http://www.lyndir.com">Lyndir</a> | <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> <a href="http://www.lyndir.com/contact">Contact</a>