Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d429044f64 | ||
|
|
b38e8d9ea6 | ||
|
|
c928b1ca2c | ||
|
|
553a14dced | ||
|
|
8b8d727ee0 | ||
|
|
4cdeab4256 | ||
|
|
bc3aa3255e | ||
|
|
f2fdca6a03 | ||
|
|
647235616e | ||
|
|
b0b6dcc56b | ||
|
|
918a240dba | ||
|
|
830dcb45ff | ||
|
|
7be9884075 | ||
|
|
5ca0d954bb | ||
|
|
02d69261df | ||
|
|
fc60460935 | ||
|
|
559e11b16e | ||
|
|
0a72809b02 | ||
|
|
8c71ed0081 | ||
|
|
3e19a026ba | ||
|
|
8fa3c6c75d | ||
|
|
217cf56d94 | ||
|
|
6f37f28a4c | ||
|
|
3b7d2dc08e | ||
|
|
5e9af44736 | ||
|
|
be33a29fa0 | ||
|
|
29ed22d0b7 | ||
|
|
8b997528c9 | ||
|
|
04a6c8e68d | ||
|
|
0e8e4dc06d | ||
|
|
8b91c2a0b8 | ||
|
|
e967affddb | ||
|
|
dea7434bd4 | ||
|
|
1da63e450d | ||
|
|
029d240999 | ||
|
|
110f7069e1 | ||
|
|
d77cde1929 |
8
.gitignore
vendored
@@ -20,6 +20,14 @@
|
||||
!/*.xcodeproj/project.xcworkspace/*
|
||||
/*.xcodeproj/project.xcworkspace/xcuserdata
|
||||
|
||||
# Media
|
||||
Press/Background.png
|
||||
Press/Front-Page.png
|
||||
Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf
|
||||
|
||||
# IPA
|
||||
/sendipa/*
|
||||
!/sendipa/sendipa.conf
|
||||
|
||||
# Java
|
||||
MasterPassword/Java/**/target
|
||||
|
||||
2
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -2,7 +2,9 @@
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="false" />
|
||||
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodIsLaterInTheScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>11C74</string>
|
||||
<string>11E53</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.2</string>
|
||||
<string>1.1.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
@@ -25,7 +25,7 @@
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0101.02.00</string>
|
||||
<string>0101.05.00</string>
|
||||
<key>CrashlyticsAPIKey</key>
|
||||
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
|
||||
<key>DTCompiler</key>
|
||||
|
||||
2
External/Pearl
vendored
2
External/iCloudStoreManager
vendored
@@ -7,12 +7,20 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E81EFABC6085AC8AE69 /* MPKey.m */; };
|
||||
93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */; };
|
||||
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */; };
|
||||
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; };
|
||||
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
|
||||
DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; };
|
||||
DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; };
|
||||
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
|
||||
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
|
||||
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
|
||||
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3215B55397007ED9BC /* MPUserEntity.m */; };
|
||||
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */; };
|
||||
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */; };
|
||||
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */; };
|
||||
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
|
||||
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
|
||||
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
|
||||
@@ -20,16 +28,19 @@
|
||||
DA30E9D415722EF400A68B4C /* Pearl-UIKit.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */; };
|
||||
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
|
||||
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2601586099D0079CE6E /* MPUserEntity.m */; };
|
||||
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2631586099E0079CE6E /* MPElementEntity.m */; };
|
||||
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */; };
|
||||
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */; };
|
||||
DA3EF17B15A47744003ABF4E /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */; };
|
||||
DA3EF17C15A47744003ABF4E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA48147E415C00F98B1E /* UIKit.framework */; };
|
||||
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA3EF18315A47744003ABF4E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA3EF18115A47744003ABF4E /* InfoPlist.strings */; };
|
||||
DA3EF18615A47744003ABF4E /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3EF18515A47744003ABF4E /* Tests.m */; };
|
||||
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; };
|
||||
DA4426011557BF260052177D /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4425F21557BF260052177D /* UbiquityStoreManager.m */; };
|
||||
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4426051557C1990052177D /* MPAppDelegate_Shared.m */; };
|
||||
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4426071557C1990052177D /* MPAppDelegate_Store.m */; };
|
||||
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libiCloudStoreManager.a */; };
|
||||
DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */; };
|
||||
DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */; };
|
||||
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; };
|
||||
DA4DA1DA1564471F00F6F596 /* libuicolor-utilities.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */; };
|
||||
DA4DA1DB1564475E00F6F596 /* libscryptenc-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */; };
|
||||
@@ -97,7 +108,6 @@
|
||||
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45115036BCF00CED3BC /* MPTypeViewController.m */; };
|
||||
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */; };
|
||||
DAB8D46A15036BCF00CED3BC /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D45415036BCF00CED3BC /* Settings.bundle */; };
|
||||
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D45615036BCF00CED3BC /* MPTypes.m */; };
|
||||
DAB8D6FA15036BF600CED3BC /* ui_background.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47115036BF600CED3BC /* ui_background.png */; };
|
||||
DAB8D6FB15036BF600CED3BC /* ui_background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47215036BF600CED3BC /* ui_background@2x.png */; };
|
||||
DAB8D6FC15036BF600CED3BC /* ui_box_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D47315036BF600CED3BC /* ui_box_checked.png */; };
|
||||
@@ -670,6 +680,7 @@
|
||||
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 */; };
|
||||
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
|
||||
DAC4149215C53C48007A716E /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DAC4149115C53C48007A716E /* dictionary.lst */; };
|
||||
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
|
||||
@@ -828,6 +839,13 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
DA3EF19D15A47AEB003ABF4E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DA5BFA43147E415C00F98B1E;
|
||||
remoteInfo = MasterPassword;
|
||||
};
|
||||
DA4DA1D71564470200F6F596 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA79A9BD1557DDC700BAA07A /* scrypt.xcodeproj */;
|
||||
@@ -873,6 +891,14 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = "<group>"; };
|
||||
93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = "<group>"; };
|
||||
93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = "<group>"; };
|
||||
93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = "<group>"; };
|
||||
93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
|
||||
93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV1.h; sourceTree = "<group>"; };
|
||||
93D39E81EFABC6085AC8AE69 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
|
||||
93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV1.m; sourceTree = "<group>"; };
|
||||
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
|
||||
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; };
|
||||
@@ -886,6 +912,14 @@
|
||||
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
|
||||
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
|
||||
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
|
||||
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
|
||||
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
|
||||
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
|
||||
@@ -893,14 +927,13 @@
|
||||
DA30E9D315722EF400A68B4C /* Pearl-UIKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-UIKit.m"; sourceTree = "<group>"; };
|
||||
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
|
||||
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
|
||||
DA40C25F1586099D0079CE6E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2601586099D0079CE6E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2621586099E0079CE6E /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2631586099E0079CE6E /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA3EF17915A47744003ABF4E /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
DA3EF18015A47744003ABF4E /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
|
||||
DA3EF18215A47744003ABF4E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DA3EF18415A47744003ABF4E /* Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tests.h; sourceTree = "<group>"; };
|
||||
DA3EF18515A47744003ABF4E /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
|
||||
DA3EF18715A47744003ABF4E /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
|
||||
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libiCloudStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA4425F11557BF260052177D /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
|
||||
DA4425F21557BF260052177D /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = "<group>"; };
|
||||
@@ -908,6 +941,9 @@
|
||||
DA4426051557C1990052177D /* MPAppDelegate_Shared.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Shared.m; sourceTree = "<group>"; };
|
||||
DA4426061557C1990052177D /* MPAppDelegate_Store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_Store.h; sourceTree = "<group>"; };
|
||||
DA4426071557C1990052177D /* MPAppDelegate_Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Store.m; sourceTree = "<group>"; };
|
||||
DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_bottom_right.png; sourceTree = "<group>"; };
|
||||
DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_bottom_right@2x.png"; sourceTree = "<group>"; };
|
||||
DA5BFA44147E415C00F98B1E /* MasterPassword.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MasterPassword.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA5BFA48147E415C00F98B1E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
@@ -963,7 +999,7 @@
|
||||
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
|
||||
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
|
||||
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MasterPassword.xcdatamodel; sourceTree = "<group>"; };
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DAB8D44115036BCF00CED3BC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
DAB8D44215036BCF00CED3BC /* MainStoryboard_iPhone.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MainStoryboard_iPhone.storyboard; sourceTree = "<group>"; };
|
||||
@@ -985,7 +1021,6 @@
|
||||
DAB8D45215036BCF00CED3BC /* MPUnlockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUnlockViewController.h; sourceTree = "<group>"; };
|
||||
DAB8D45315036BCF00CED3BC /* MPUnlockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUnlockViewController.m; sourceTree = "<group>"; };
|
||||
DAB8D45415036BCF00CED3BC /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
DAB8D45615036BCF00CED3BC /* MPTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTypes.m; sourceTree = "<group>"; };
|
||||
DAB8D45915036BCF00CED3BC /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
|
||||
DAB8D47115036BF600CED3BC /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
|
||||
DAB8D47215036BF600CED3BC /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
|
||||
@@ -1628,6 +1663,7 @@
|
||||
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>"; };
|
||||
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
||||
DAC4149115C53C48007A716E /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; };
|
||||
DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libuicolor-utilities.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DAC6326C148680650075AEA5 /* libjrswizzle.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjrswizzle.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -1787,6 +1823,16 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
DA3EF17515A47744003ABF4E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA3EF17B15A47744003ABF4E /* SenTestingKit.framework in Frameworks */,
|
||||
DA3EF17C15A47744003ABF4E /* UIKit.framework in Frameworks */,
|
||||
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA4425C81557BED40052177D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1874,6 +1920,26 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
DA3EF17E15A47744003ABF4E /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3EF18415A47744003ABF4E /* Tests.h */,
|
||||
DA3EF18515A47744003ABF4E /* Tests.m */,
|
||||
DA3EF17F15A47744003ABF4E /* Supporting Files */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA3EF17F15A47744003ABF4E /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3EF18015A47744003ABF4E /* Tests-Info.plist */,
|
||||
DA3EF18115A47744003ABF4E /* InfoPlist.strings */,
|
||||
DA3EF18715A47744003ABF4E /* Tests-Prefix.pch */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA4425D71557BF260052177D /* iCloudStoreManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1900,6 +1966,7 @@
|
||||
DAD3127315528CD200A3F9ED /* Localytics */,
|
||||
DA4425D71557BF260052177D /* iCloudStoreManager */,
|
||||
DA829E5D15984812002417D3 /* FontReplacer */,
|
||||
DA3EF17E15A47744003ABF4E /* Tests */,
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA45147E415C00F98B1E /* Products */,
|
||||
);
|
||||
@@ -1916,6 +1983,7 @@
|
||||
DAD3127115528CD200A3F9ED /* libLocalytics.a */,
|
||||
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */,
|
||||
DA829E51159847E0002417D3 /* libFontReplacer.a */,
|
||||
DA3EF17915A47744003ABF4E /* Tests.octest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -1935,6 +2003,7 @@
|
||||
DA5BFA4A147E415C00F98B1E /* Foundation.framework */,
|
||||
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */,
|
||||
DA5BFA4E147E415C00F98B1E /* CoreData.framework */,
|
||||
DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -1942,17 +2011,25 @@
|
||||
DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */,
|
||||
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */,
|
||||
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */,
|
||||
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */,
|
||||
DA40C2621586099E0079CE6E /* MPElementEntity.h */,
|
||||
DA40C2631586099E0079CE6E /* MPElementEntity.m */,
|
||||
DA40C25F1586099D0079CE6E /* MPUserEntity.h */,
|
||||
DA40C2601586099D0079CE6E /* MPUserEntity.m */,
|
||||
93D39D0EF77FEC36EA0FB334 /* MPAlgorithmV1.h */,
|
||||
93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */,
|
||||
93D39B0DF5E3C56355186738 /* MPAlgorithm.m */,
|
||||
93D39C68AFA48A13015E4FAC /* MPKey.h */,
|
||||
93D39E81EFABC6085AC8AE69 /* MPKey.m */,
|
||||
93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */,
|
||||
93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */,
|
||||
93D398E394E311C545E0A057 /* MPAlgorithm.h */,
|
||||
DA0E07941577FE490008A67E /* MPEntities.h */,
|
||||
DA0E07951577FE490008A67E /* MPEntities.m */,
|
||||
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
|
||||
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */,
|
||||
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */,
|
||||
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */,
|
||||
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */,
|
||||
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */,
|
||||
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */,
|
||||
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */,
|
||||
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */,
|
||||
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
|
||||
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
|
||||
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
|
||||
@@ -1962,7 +2039,6 @@
|
||||
DA600C2615056427008E9AB6 /* MPConfig.h */,
|
||||
DA600C2715056427008E9AB6 /* MPConfig.m */,
|
||||
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
|
||||
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
|
||||
DAB8D43E15036BCF00CED3BC /* iOS */,
|
||||
);
|
||||
path = MasterPassword;
|
||||
@@ -2141,6 +2217,7 @@
|
||||
DAB8D46F15036BF600CED3BC /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAC4149115C53C48007A716E /* dictionary.lst */,
|
||||
DA902BD01576CA4A00C38161 /* keypad.png */,
|
||||
DA902B931576C0FB00C38161 /* Avatars */,
|
||||
DAB8D47015036BF600CED3BC /* Automaton */,
|
||||
@@ -2780,6 +2857,8 @@
|
||||
DAB8D6B715036BF600CED3BC /* Tooltips */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */,
|
||||
DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */,
|
||||
DACABB8E1572B769008BA211 /* tip_basic_black_top.png */,
|
||||
DACABB8F1572B769008BA211 /* tip_basic_black_top@2x.png */,
|
||||
DACABB8A1572A4A4008BA211 /* tip_basic_black_top_right.png */,
|
||||
@@ -3222,6 +3301,25 @@
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
DA3EF17815A47744003ABF4E /* Tests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA3EF18815A47744003ABF4E /* Build configuration list for PBXNativeTarget "Tests" */;
|
||||
buildPhases = (
|
||||
DA3EF17415A47744003ABF4E /* Sources */,
|
||||
DA3EF17515A47744003ABF4E /* Frameworks */,
|
||||
DA3EF17615A47744003ABF4E /* Resources */,
|
||||
DA3EF17715A47744003ABF4E /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DA3EF19E15A47AEB003ABF4E /* PBXTargetDependency */,
|
||||
);
|
||||
name = Tests;
|
||||
productName = Tests;
|
||||
productReference = DA3EF17915A47744003ABF4E /* Tests.octest */;
|
||||
productType = "com.apple.product-type.bundle";
|
||||
};
|
||||
DA4425CA1557BED40052177D /* iCloudStoreManager */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA4425D31557BED40052177D /* Build configuration list for PBXNativeTarget "iCloudStoreManager" */;
|
||||
@@ -3400,6 +3498,7 @@
|
||||
DAD3127015528CD200A3F9ED /* Localytics */,
|
||||
DA4425CA1557BED40052177D /* iCloudStoreManager */,
|
||||
DA829E50159847E0002417D3 /* FontReplacer */,
|
||||
DA3EF17815A47744003ABF4E /* Tests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -3429,6 +3528,14 @@
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
DA3EF17615A47744003ABF4E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA3EF18315A47744003ABF4E /* InfoPlist.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA5BFA42147E415C00F98B1E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -4068,12 +4175,28 @@
|
||||
DAE4C98C157E63BE00EFE047 /* avatar-17@2x.png in Resources */,
|
||||
DAE4C98D157E63BE00EFE047 /* avatar-18.png in Resources */,
|
||||
DAE4C98E157E63BE00EFE047 /* avatar-18@2x.png in Resources */,
|
||||
DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */,
|
||||
DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */,
|
||||
DAC4149215C53C48007A716E /* dictionary.lst in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
DA3EF17715A47744003ABF4E /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n";
|
||||
};
|
||||
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -4107,6 +4230,14 @@
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
DA3EF17415A47744003ABF4E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA3EF18615A47744003ABF4E /* Tests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA4425C71557BED40052177D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -4128,17 +4259,20 @@
|
||||
DAB8D46715036BCF00CED3BC /* MPSearchDelegate.m in Sources */,
|
||||
DAB8D46815036BCF00CED3BC /* MPTypeViewController.m in Sources */,
|
||||
DAB8D46915036BCF00CED3BC /* MPUnlockViewController.m in Sources */,
|
||||
DAB8D46C15036BCF00CED3BC /* MPTypes.m in Sources */,
|
||||
DA600C2515054F3A008E9AB6 /* MPAppDelegate_Key.m in Sources */,
|
||||
DA600C2815056428008E9AB6 /* MPConfig.m in Sources */,
|
||||
DA4426081557C1990052177D /* MPAppDelegate_Shared.m in Sources */,
|
||||
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
|
||||
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
|
||||
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
|
||||
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */,
|
||||
DA40C2641586099E0079CE6E /* MPElementEntity.m in Sources */,
|
||||
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */,
|
||||
93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */,
|
||||
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */,
|
||||
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */,
|
||||
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */,
|
||||
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */,
|
||||
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */,
|
||||
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -4252,6 +4386,11 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
DA3EF19E15A47AEB003ABF4E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DA5BFA43147E415C00F98B1E /* MasterPassword */;
|
||||
targetProxy = DA3EF19D15A47AEB003ABF4E /* PBXContainerItemProxy */;
|
||||
};
|
||||
DA4DA1D81564470200F6F596 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = "Makefile-scrypt";
|
||||
@@ -4270,6 +4409,14 @@
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
DA3EF18115A47744003ABF4E /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
DA3EF18215A47744003ABF4E /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAB8D43F15036BCF00CED3BC /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -4289,6 +4436,88 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
DA3EF18915A47744003ABF4E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MasterPassword.app/MasterPassword";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(DEVELOPER_LIBRARY_DIR)/Frameworks",
|
||||
"\"$(SRCROOT)/Crashlytics\"",
|
||||
);
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
INFOPLIST_FILE = "Tests/Tests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DA3EF18A15A47744003ABF4E /* AdHoc */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MasterPassword.app/MasterPassword";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(DEVELOPER_LIBRARY_DIR)/Frameworks",
|
||||
"\"$(SRCROOT)/Crashlytics\"",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "Tests/Tests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = AdHoc;
|
||||
};
|
||||
DA3EF18B15A47744003ABF4E /* AppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
|
||||
BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MasterPassword.app/MasterPassword";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(DEVELOPER_LIBRARY_DIR)/Frameworks",
|
||||
"\"$(SRCROOT)/Crashlytics\"",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
|
||||
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "Tests/Tests-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 5.1;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TEST_HOST = "$(BUNDLE_LOADER)";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = octest;
|
||||
};
|
||||
name = AppStore;
|
||||
};
|
||||
DA4425D41557BED40052177D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -4757,6 +4986,16 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
DA3EF18815A47744003ABF4E /* Build configuration list for PBXNativeTarget "Tests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DA3EF18915A47744003ABF4E /* Debug */,
|
||||
DA3EF18A15A47744003ABF4E /* AdHoc */,
|
||||
DA3EF18B15A47744003ABF4E /* AppStore */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = AdHoc;
|
||||
};
|
||||
DA4425D31557BED40052177D /* Build configuration list for PBXNativeTarget "iCloudStoreManager" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -4853,9 +5092,10 @@
|
||||
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */,
|
||||
DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */,
|
||||
DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */;
|
||||
currentVersion = DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */;
|
||||
path = MasterPassword.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "NO"
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
@@ -28,6 +28,16 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA3EF17815A47744003ABF4E"
|
||||
BuildableName = "Tests.octest"
|
||||
BlueprintName = "Tests"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -44,7 +54,7 @@
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "AppStore"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
|
||||
49
MasterPassword/Java/masterpassword-algorithm/pom.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Algorithm Implementation</name>
|
||||
<description>The implementation of the Master Password algorithm</description>
|
||||
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-system</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-crypto</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>net.sf.plist</groupId>
|
||||
<artifactId>property-list</artifactId>
|
||||
<version>svn-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPElementFeature {
|
||||
|
||||
/** Export the key-protected content data. */
|
||||
ExportContent,
|
||||
/** Never export content. */
|
||||
DevicePrivate,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPElementType {
|
||||
|
||||
GeneratedMaximum( "Maximum Security Password", "Maximum", "20 characters, contains symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedLong( "Long Password", "Long", "Copy-friendly, 14 characters, contains symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedMedium( "Medium Password", "Medium", "Copy-friendly, 8 characters, contains symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedShort( "Short Password", "Short", "Copy-friendly, 4 characters, no symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedBasic( "Basic Password", "Basic", "8 characters, no symbols.", MPElementTypeClass.Generated ),
|
||||
GeneratedPIN( "PIN", "PIN", "4 numbers.", MPElementTypeClass.Generated ),
|
||||
|
||||
StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored, MPElementFeature.ExportContent ),
|
||||
StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored, MPElementFeature.DevicePrivate );
|
||||
|
||||
static final Logger logger = Logger.get( MPElementType.class );
|
||||
|
||||
private final MPElementTypeClass typeClass;
|
||||
private final Set<MPElementFeature> typeFeatures;
|
||||
private final String name;
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
|
||||
MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass, final MPElementFeature... typeFeatures) {
|
||||
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
this.typeClass = typeClass;
|
||||
this.description = description;
|
||||
|
||||
ImmutableSet.Builder<MPElementFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
||||
for (final MPElementFeature typeFeature : typeFeatures)
|
||||
typeFeaturesBuilder.add( typeFeature );
|
||||
this.typeFeatures = typeFeaturesBuilder.build();
|
||||
}
|
||||
|
||||
public MPElementTypeClass getTypeClass() {
|
||||
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
public Set<MPElementFeature> getTypeFeatures() {
|
||||
|
||||
return typeFeatures;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
public static MPElementType forName(final String name) {
|
||||
|
||||
for (final MPElementType type : values())
|
||||
if (type.getName().equals( name ))
|
||||
return type;
|
||||
|
||||
throw logger.bug( "Element type not known: %s", name );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.masterpassword.entity.*;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPElementTypeClass {
|
||||
|
||||
Generated(MPElementGeneratedEntity.class),
|
||||
Stored(MPElementStoredEntity.class);
|
||||
|
||||
private final Class<? extends MPElementEntity> entityClass;
|
||||
|
||||
MPElementTypeClass(final Class<? extends MPElementEntity> entityClass) {
|
||||
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
public Class<? extends MPElementEntity> getEntityClass() {
|
||||
|
||||
return entityClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplate extends MetaObject {
|
||||
|
||||
private final List<MPTemplateCharacterClass> template;
|
||||
|
||||
public MPTemplate(final String template, final Map<Character, MPTemplateCharacterClass> characterClasses) {
|
||||
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.<MPTemplateCharacterClass>builder();
|
||||
for (int i = 0; i < template.length(); ++i)
|
||||
builder.add( characterClasses.get( template.charAt( i ) ) );
|
||||
|
||||
this.template = builder.build();
|
||||
}
|
||||
|
||||
public MPTemplate(final List<MPTemplateCharacterClass> template) {
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
|
||||
|
||||
return template.get( index );
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
||||
return template.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplateCharacterClass extends MetaObject {
|
||||
|
||||
private final char identifier;
|
||||
@ObjectMeta(useFor = { })
|
||||
private final char[] characters;
|
||||
|
||||
public MPTemplateCharacterClass(final char identifier, final char[] characters) {
|
||||
|
||||
this.identifier = identifier;
|
||||
this.characters = characters;
|
||||
}
|
||||
|
||||
public char getIdentifier() {
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public char getCharacterAtRollingIndex(final int index) {
|
||||
|
||||
return characters[index % characters.length];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Closeables;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.sf.plist.*;
|
||||
import net.sf.plist.io.PropertyListException;
|
||||
import net.sf.plist.io.PropertyListParser;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplates extends MetaObject {
|
||||
|
||||
static final Logger logger = Logger.get( MPTemplates.class );
|
||||
|
||||
private final Map<MPElementType, List<MPTemplate>> templates;
|
||||
|
||||
public MPTemplates(final Map<MPElementType, List<MPTemplate>> templates) {
|
||||
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
public static MPTemplates loadFromPList(final String templateResource) {
|
||||
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource );
|
||||
try {
|
||||
NSObject plistObject = PropertyListParser.parse( templateStream );
|
||||
Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) );
|
||||
NSDictionary plist = (NSDictionary) plistObject;
|
||||
|
||||
NSDictionary characterClassesDict = (NSDictionary) plist.get( "MPCharacterClasses" );
|
||||
NSDictionary templatesDict = (NSDictionary) plist.get( "MPElementGeneratedEntity" );
|
||||
|
||||
ImmutableMap.Builder<Character, MPTemplateCharacterClass> characterClassesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> characterClassEntry : characterClassesDict.entrySet()) {
|
||||
String key = characterClassEntry.getKey();
|
||||
NSObject value = characterClassEntry.getValue();
|
||||
Preconditions.checkState( key.length() == 1 );
|
||||
Preconditions.checkState( NSString.class.isAssignableFrom( value.getClass() ));
|
||||
|
||||
char character = key.charAt( 0 );
|
||||
char[] characterClass = ((NSString)value).getValue().toCharArray();
|
||||
characterClassesBuilder.put( character, new MPTemplateCharacterClass( character, characterClass ) );
|
||||
}
|
||||
ImmutableMap<Character, MPTemplateCharacterClass> characterClasses = characterClassesBuilder.build();
|
||||
|
||||
ImmutableMap.Builder<MPElementType, List<MPTemplate>> templatesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> template : templatesDict.entrySet()) {
|
||||
String key = template.getKey();
|
||||
NSObject value = template.getValue();
|
||||
Preconditions.checkState( NSArray.class.isAssignableFrom( value.getClass() ) );
|
||||
|
||||
MPElementType type = MPElementType.forName( key );
|
||||
List<NSObject> templateStrings = ((NSArray) value).getValue();
|
||||
|
||||
ImmutableList.Builder<MPTemplate> typeTemplatesBuilder = ImmutableList.<MPTemplate>builder();
|
||||
for (final NSObject templateString : templateStrings)
|
||||
typeTemplatesBuilder.add( new MPTemplate( ((NSString) templateString).getValue(), characterClasses ) );
|
||||
|
||||
templatesBuilder.put( type, typeTemplatesBuilder.build() );
|
||||
}
|
||||
ImmutableMap<MPElementType, List<MPTemplate>> templates = templatesBuilder.build();
|
||||
|
||||
return new MPTemplates( templates );
|
||||
}
|
||||
catch (PropertyListException e) {
|
||||
logger.err( e, "Could not parse templates from: %s", templateResource );
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.err( e, "Could not read templates from: %s", templateResource );
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
finally {
|
||||
Closeables.closeQuietly( templateStream );
|
||||
}
|
||||
}
|
||||
|
||||
public MPTemplate getTemplateForTypeAtRollingIndex(final MPElementType type, final int templateIndex) {
|
||||
|
||||
List<MPTemplate> typeTemplates = templates.get( type );
|
||||
|
||||
return typeTemplates.get( templateIndex % typeTemplates.size() );
|
||||
}
|
||||
|
||||
public static void main(final String... arguments) {
|
||||
|
||||
loadFromPList( "templates.plist" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.lambdaworks.crypto.SCrypt;
|
||||
import com.lyndir.lhunath.opal.crypto.CryptUtils;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the Master Password algorithm.
|
||||
*
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public abstract class MasterPassword {
|
||||
|
||||
static final Logger logger = Logger.get( MasterPassword.class );
|
||||
private static final int MP_N = 32768;
|
||||
private static final int MP_r = 8;
|
||||
private static final int MP_p = 2;
|
||||
private static final int MP_dkLen = 64;
|
||||
private static final Charset MP_charset = Charsets.UTF_8;
|
||||
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
|
||||
private static final MessageDigests MP_hash = MessageDigests.SHA256;
|
||||
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
|
||||
private static final MPTemplates templates = MPTemplates.loadFromPList( "templates.plist" );
|
||||
|
||||
public static byte[] keyForPassword(final String password, final String username) {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
byte[] nusernameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE )
|
||||
.order( MP_byteOrder )
|
||||
.putInt( username.length() )
|
||||
.array();
|
||||
byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||
nusernameLengthBytes, //
|
||||
username.getBytes( MP_charset ) );
|
||||
|
||||
try {
|
||||
byte[] key = SCrypt.scrypt( password.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||
logger.trc( "User: %s, password: %s derives to key ID: %s (took %.2fs)", username, password,
|
||||
CodeUtils.encodeHex( keyIDForKey( key ) ), (double) (System.currentTimeMillis() - start) / 1000 );
|
||||
|
||||
return key;
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] subkeyForKey(final byte[] key, final int subkeyLength) {
|
||||
|
||||
byte[] subkey = new byte[Math.min( subkeyLength, key.length )];
|
||||
System.arraycopy( key, 0, subkey, 0, subkey.length );
|
||||
|
||||
return subkey;
|
||||
}
|
||||
|
||||
public static byte[] keyIDForPassword(final String password, final String username) {
|
||||
|
||||
return keyIDForKey( keyForPassword( password, username ) );
|
||||
}
|
||||
|
||||
public static byte[] keyIDForKey(final byte[] key) {
|
||||
|
||||
return MP_hash.of( key );
|
||||
}
|
||||
|
||||
public static String generateContent(final MPElementType type, final String name, final byte[] key, int counter) {
|
||||
|
||||
Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated );
|
||||
Preconditions.checkArgument( !name.isEmpty() );
|
||||
Preconditions.checkArgument( key.length > 0 );
|
||||
|
||||
if (counter == 0)
|
||||
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
|
||||
byte[] nameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( name.length() ).array();
|
||||
byte[] counterBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( counter ).array();
|
||||
logger.trc( "seed from: hmac-sha256(%s, 'com.lyndir.masterpassword' | %s | %s | %s)", CryptUtils.encodeBase64( key ),
|
||||
CodeUtils.encodeHex( nameLengthBytes ), name, CodeUtils.encodeHex( counterBytes ) );
|
||||
byte[] seed = MP_mac.of( key, Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||
nameLengthBytes, //
|
||||
name.getBytes( MP_charset ), //
|
||||
counterBytes ) );
|
||||
logger.trc( "seed is: %s", CryptUtils.encodeBase64( seed ) );
|
||||
|
||||
Preconditions.checkState( seed.length > 0 );
|
||||
int templateIndex = seed[0] & 0xFF; // Mask the integer's sign.
|
||||
MPTemplate template = templates.getTemplateForTypeAtRollingIndex( type, templateIndex );
|
||||
logger.trc( "type: %s, template: %s", type, template );
|
||||
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = seed[i + 1] & 0xFF; // Mask the integer's sign.
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( "class: %s, index: %d, byte: 0x%02X, chosen password character: %s", characterClass, characterIndex, seed[i + 1],
|
||||
passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
public static void main(final String... arguments) {
|
||||
|
||||
String masterPassword = "test-mp";
|
||||
String username = "test-user";
|
||||
String siteName = "test-site";
|
||||
MPElementType siteType = MPElementType.GeneratedLong;
|
||||
int siteCounter = 42;
|
||||
|
||||
String sitePassword = generateContent( siteType, siteName, keyForPassword( masterPassword, username ), siteCounter );
|
||||
|
||||
logger.inf( "master password: %s, username: %s\nsite name: %s, site type: %s, site counter: %d\n => site password: %s",
|
||||
masterPassword, username, siteName, siteType, siteCounter, sitePassword );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lyndir.lhunath.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lyndir.lhunath.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementGeneratedEntity extends MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.lyndir.lhunath.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementStoredEntity extends MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../Resources/ciphers.plist
|
||||
80
MasterPassword/Java/masterpassword-cli/pom.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password CLI</name>
|
||||
<description>A CLI interface to the Master Password algorithm</description>
|
||||
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword-cli</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- BUILD CONFIGURATION -->
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/scripts</directory>
|
||||
<filtering>true</filtering>
|
||||
<targetPath>${project.build.directory}</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.lyndir.lhunath.masterpassword.CLI</mainClass>
|
||||
<addClasspath>true</addClasspath>
|
||||
<classpathPrefix>lib/</classpathPrefix>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2008, Maarten Billemont
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.lyndir.lhunath.masterpassword;
|
||||
|
||||
import com.google.common.io.LineReader;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* <p> <i>Jun 10, 2008</i> </p>
|
||||
*
|
||||
* @author mbillemo
|
||||
*/
|
||||
public class CLI {
|
||||
|
||||
static final Logger logger = Logger.get( CLI.class );
|
||||
private static final String ENV_USERNAME = "MP_USERNAME";
|
||||
private static final String ENV_PASSWORD = "MP_PASSWORD";
|
||||
|
||||
public static void main(final String[] args)
|
||||
throws IOException {
|
||||
|
||||
String userName, masterPassword, siteName = null;
|
||||
|
||||
/* Environment. */
|
||||
userName = System.getenv().get( ENV_USERNAME );
|
||||
masterPassword = System.getenv().get( ENV_PASSWORD );
|
||||
|
||||
/* Arguments. */
|
||||
int counter = 1;
|
||||
MPElementType type = MPElementType.GeneratedLong;
|
||||
boolean typeArg = false, counterArg = false, userNameArg = false;
|
||||
for (final String arg : Arrays.asList( args ))
|
||||
if ("-t".equals( arg ) || "--type".equals( arg ))
|
||||
typeArg = true;
|
||||
else if (typeArg) {
|
||||
if ("list".equalsIgnoreCase( arg )) {
|
||||
System.out.format( "%30s | %s\n", "type", "description" );
|
||||
for (final MPElementType aType : MPElementType.values())
|
||||
System.out.format( "%30s | %s\n", aType.getName(), aType.getDescription() );
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
type = MPElementType.forName( arg );
|
||||
typeArg = false;
|
||||
} else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
||||
counterArg = true;
|
||||
else if (counterArg) {
|
||||
counter = ConversionUtils.toIntegerNN( arg );
|
||||
counterArg = false;
|
||||
} else if ("-u".equals( arg ) || "--username".equals( arg ))
|
||||
userNameArg = true;
|
||||
else if (userNameArg) {
|
||||
userName = arg;
|
||||
userNameArg = false;
|
||||
} else if ("-h".equals( arg ) || "--help".equals( arg )) {
|
||||
System.out.println();
|
||||
System.out.println( "\tMaster Password CLI" );
|
||||
System.out.println( "\t\tLyndir" );
|
||||
|
||||
System.out.println( "[options] [site name]" );
|
||||
System.out.println();
|
||||
System.out.println( "Available options:" );
|
||||
|
||||
System.out.println( "\t-t | --type [site password type]" );
|
||||
System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", type.getName() );
|
||||
System.out.println( "\t\tUse 'list' to see the available types." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "\t-c | --counter [site counter]" );
|
||||
System.out.format( "\t\tDefault: %d. The counter to use for this site.\n", counter );
|
||||
System.out.println( "\t\tIncrement the counter if you need a new password." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "\t-u | --username [user's name]" );
|
||||
System.out.println( "\t\tDefault: asked. The name of the user." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "Available environment variables:" );
|
||||
|
||||
System.out.format( "\t%s\n", ENV_USERNAME );
|
||||
System.out.println( "\t\tThe name of the user." );
|
||||
|
||||
System.out.format( "\t%s\n", ENV_PASSWORD );
|
||||
System.out.println( "\t\tThe master password of the user." );
|
||||
|
||||
System.out.println();
|
||||
return;
|
||||
} else
|
||||
siteName = arg;
|
||||
|
||||
InputStreamReader inReader = new InputStreamReader( System.in );
|
||||
try {
|
||||
LineReader lineReader = new LineReader( inReader );
|
||||
if (siteName == null) {
|
||||
System.err.format( "Site name: " );
|
||||
siteName = lineReader.readLine();
|
||||
}
|
||||
if (userName == null) {
|
||||
System.err.format( "User's name: " );
|
||||
userName = lineReader.readLine();
|
||||
}
|
||||
if (masterPassword == null) {
|
||||
System.err.format( "%s's master password: ", userName );
|
||||
masterPassword = lineReader.readLine();
|
||||
}
|
||||
|
||||
byte[] masterKey = MasterPassword.keyForPassword( masterPassword, userName );
|
||||
String sitePassword = MasterPassword.generateContent( type, siteName, masterKey, counter );
|
||||
System.out.println( sitePassword );
|
||||
}
|
||||
finally {
|
||||
inReader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<configuration scan="true">
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="TRACE" />
|
||||
|
||||
<!--
|
||||
<logger name="org.apache.wicket" level="DEBUG" />
|
||||
-->
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
4
MasterPassword/Java/masterpassword-cli/src/main/scripts/mpw
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd "${BASH_SOURCE[0]%/*}"
|
||||
java -jar masterpassword-cli-GIT-SNAPSHOT.jar "$@"
|
||||
26
MasterPassword/Java/pom.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath</groupId>
|
||||
<artifactId>lyndir</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password</name>
|
||||
<description>A Java implementation of the Master Password algorithm.</description>
|
||||
|
||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>masterpassword-algorithm</module>
|
||||
<module>masterpassword-cli</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
44
MasterPassword/MPAlgorithm.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithm
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPKey.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
|
||||
#define MPAlgorithmDefaultVersion 1
|
||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
||||
|
||||
@protocol MPAlgorithm<NSObject>
|
||||
|
||||
@required
|
||||
- (NSUInteger)version;
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||
|
||||
- (NSString *)nameOfType:(MPElementType)type;
|
||||
- (NSString *)shortNameOfType:(MPElementType)type;
|
||||
- (NSString *)classNameOfType:(MPElementType)type;
|
||||
- (Class)classOfType:(MPElementType)type;
|
||||
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
|
||||
|
||||
@end
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
||||
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion);
|
||||
42
MasterPassword/MPAlgorithm.m
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithm
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithm.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
|
||||
|
||||
static NSMutableDictionary *versionToAlgorithm = nil;
|
||||
if (!versionToAlgorithm)
|
||||
versionToAlgorithm = [NSMutableDictionary dictionary];
|
||||
|
||||
id<MPAlgorithm> algorithm = [versionToAlgorithm objectForKey:PearlUnsignedInteger(version)];
|
||||
if (!algorithm)
|
||||
if ((algorithm = [NSClassFromString(PearlString(@"MPAlgorithmV%u", version)) new]))
|
||||
[versionToAlgorithm setObject:algorithm forKey:PearlUnsignedInteger(version)];
|
||||
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
|
||||
|
||||
if (PearlCFBundleVersionCompare(bundleVersion, @"1.3") == NSOrderedAscending)
|
||||
// Pre-1.3
|
||||
return MPAlgorithmForVersion(0);
|
||||
|
||||
return MPAlgorithmDefault;
|
||||
}
|
||||
21
MasterPassword/MPAlgorithmV0.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV0
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
@interface MPAlgorithmV0 : NSObject <MPAlgorithm>
|
||||
@end
|
||||
@@ -1,54 +1,83 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPTypes.m
|
||||
// MasterPassword
|
||||
// MPAlgorithmV0
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV0.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
|
||||
#define MP_N 131072
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 1
|
||||
#define MP_p 2
|
||||
#define MP_dkLen 64
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
NSData *keyForPassword(NSString *password, NSString *username) {
|
||||
@implementation MPAlgorithmV0
|
||||
|
||||
uint32_t nusernameLength = htonl(username.length);
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nusernameLength
|
||||
length:sizeof(nusernameLength)],
|
||||
[username dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
- (NSUInteger)version {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
||||
|
||||
if (element.version != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
if (!explicit) {
|
||||
// This migration requires explicit permission.
|
||||
element.requiresExplicitMigration = YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Apply migration.
|
||||
element.requiresExplicitMigration = NO;
|
||||
element.version = [self version];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
|
||||
|
||||
uint32_t nuserNameLength = htonl(userName.length);
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *keyData = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nuserNameLength
|
||||
length:sizeof(nuserNameLength)],
|
||||
[userName dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
MPKey *key = [self keyFromKeyData:keyData];
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
|
||||
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2f)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
|
||||
return key;
|
||||
}
|
||||
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData {
|
||||
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
|
||||
|
||||
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
|
||||
return [[MPKey alloc] initWithKeyData:keyData algorithm:self];
|
||||
}
|
||||
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData {
|
||||
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username) {
|
||||
|
||||
return keyIDForKey(keyForPassword(password, username));
|
||||
return [keyData hashWith:MP_hash];
|
||||
}
|
||||
|
||||
NSData *keyIDForKey(NSData *key) {
|
||||
|
||||
return [key hashWith:MP_hash];
|
||||
}
|
||||
|
||||
NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
- (NSString *)nameOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
@@ -77,13 +106,12 @@ NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device Private Password";
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type) {
|
||||
- (NSString *)shortNameOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
@@ -112,13 +140,17 @@ NSString *NSStringShortFromMPElementType(MPElementType type) {
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device";
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Class ClassFromMPElementType(MPElementType type) {
|
||||
- (NSString *)classNameOfType:(MPElementType)type {
|
||||
|
||||
return NSStringFromClass([self classOfType:type]);
|
||||
}
|
||||
|
||||
- (Class)classOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
@@ -147,61 +179,56 @@ Class ClassFromMPElementType(MPElementType type) {
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return [MPElementStoredEntity class];
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
|
||||
NSString *ClassNameFromMPElementType(MPElementType type) {
|
||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
|
||||
|
||||
return NSStringFromClass(ClassFromMPElementType(type));
|
||||
}
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
if (!element)
|
||||
return nil;
|
||||
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
|
||||
|
||||
if (!(type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
|
||||
if (!(element.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %@, for: %@", [self nameOfType:element.type], element.name);
|
||||
return nil;
|
||||
}
|
||||
if (!name.length) {
|
||||
if (!element.name.length) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key.length) {
|
||||
if (!key.keyData.length) {
|
||||
err(@"Missing key.");
|
||||
return nil;
|
||||
}
|
||||
if (!counter)
|
||||
// Counter unset, go into OTP mode.
|
||||
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
|
||||
counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
|
||||
|
||||
if (MPTypes_ciphers == nil)
|
||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||
withExtension:@"plist"]];
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
|
||||
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
||||
uint32_t ncounter = htonl(element.counter), nnameLength = htonl(element.name.length);
|
||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)];
|
||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)];
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], element.name, [counterBytes encodeHex]);
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
||||
nameLengthBytes,
|
||||
[element.name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
counterBytes,
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key];
|
||||
trc(@"seed is: %@", seed);
|
||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||
trc(@"seed is: %@", [seed encodeBase64]);
|
||||
const char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||
valueForKey:NSStringFromMPElementType(type)];
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:element.type]]
|
||||
valueForKey:[self nameOfType:element.type]];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:element.type], typeCiphers, cipher);
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
@@ -213,9 +240,11 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
|
||||
1)];
|
||||
|
||||
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
|
||||
trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character);
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@end
|
||||
22
MasterPassword/MPAlgorithmV1.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV1
|
||||
//
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV0.h"
|
||||
|
||||
|
||||
@interface MPAlgorithmV1 : MPAlgorithmV0
|
||||
@end
|
||||
113
MasterPassword/MPAlgorithmV1.m
Normal file
@@ -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): %@, 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
|
||||
@@ -11,7 +11,7 @@
|
||||
@interface MPAppDelegate_Shared (Key)
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
|
||||
- (void)signOut;
|
||||
- (void)signOutAnimated:(BOOL)animated;
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||
|
||||
@@ -23,10 +23,10 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (key)
|
||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (keyData)
|
||||
inf(@"Found key in keychain for: %@", user.userID);
|
||||
|
||||
else {
|
||||
@@ -34,22 +34,22 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
inf(@"No key found in keychain for: %@", user.userID);
|
||||
}
|
||||
|
||||
return key;
|
||||
return [MPAlgorithmDefault keyFromKeyData:keyData];
|
||||
}
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
if (user.saveKey) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
NSData *existingKeyData = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
if (![existingKeyData isEqualToData:self.key.keyData]) {
|
||||
inf(@"Saving key in keychain for: %@", user.userID);
|
||||
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
self.key.keyData, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
@@ -73,26 +73,27 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
- (void)signOutAnimated:(BOOL)animated {
|
||||
|
||||
if (self.key)
|
||||
self.key = nil;
|
||||
|
||||
if (self.activeUser) {
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self userInfo:
|
||||
[NSDictionary dictionaryWithObject:PearlBool(animated) forKey:@"animated"]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
|
||||
|
||||
NSData *tryKey = nil;
|
||||
MPKey *tryKey = nil;
|
||||
|
||||
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
||||
if (!user.keyID) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name))) {
|
||||
user.keyID = keyIDForKey(tryKey);
|
||||
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
|
||||
user.keyID = tryKey.keyID;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
if (![user.keyID isEqual:tryKey.keyID]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
inf(@"Saved password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
@@ -118,8 +119,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
// Method 3: Check the given master password string.
|
||||
if (!tryKey) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name)))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name]))
|
||||
if (![user.keyID isEqual:tryKey.keyID]) {
|
||||
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
tryKey = nil;
|
||||
@@ -141,7 +142,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
}
|
||||
inf(@"Logged in: %@", user.userID);
|
||||
|
||||
if (![self.key isEqualToData:tryKey]) {
|
||||
if (![self.key isEqualToKey:tryKey]) {
|
||||
self.key = tryKey;
|
||||
[self storeSavedKeyFor:user];
|
||||
}
|
||||
@@ -159,6 +160,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
self.activeUser = user;
|
||||
self.activeUser.requiresExplicitMigration = NO;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *activeUser;
|
||||
@property (strong, nonatomic) NSData *key;
|
||||
@property (strong, nonatomic) MPKey *key;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get;
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ typedef enum {
|
||||
|
||||
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
+ (NSManagedObjectContext *)managedObjectContextIfReady;
|
||||
+ (NSManagedObjectModel *)managedObjectModel;
|
||||
- (NSManagedObjectContext *)managedObjectContext;
|
||||
- (NSManagedObjectContext *)managedObjectContextIfReady;
|
||||
- (NSManagedObjectModel *)managedObjectModel;
|
||||
|
||||
- (UbiquityStoreManager *)storeManager;
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
#pragma mark - Core Data setup
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext {
|
||||
+ (NSManagedObjectContext *)managedObjectContextIfReady {
|
||||
|
||||
return [[self get] managedObjectContext];
|
||||
return [[self get] managedObjectContextIfReady];
|
||||
}
|
||||
|
||||
+ (NSManagedObjectModel *)managedObjectModel {
|
||||
@@ -33,7 +33,10 @@
|
||||
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
}
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContext {
|
||||
- (NSManagedObjectContext *)managedObjectContextIfReady {
|
||||
|
||||
if (![self storeManager].isReady)
|
||||
return nil;
|
||||
|
||||
static NSManagedObjectContext *managedObjectContext = nil;
|
||||
if (managedObjectContext)
|
||||
@@ -41,25 +44,14 @@
|
||||
|
||||
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[managedObjectContext performBlockAndWait:^{
|
||||
managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
|
||||
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||
managedObjectContext.persistentStoreCoordinator = [self storeManager].persistentStoreCoordinator;
|
||||
managedObjectContext.undoManager = [NSUndoManager new];
|
||||
}];
|
||||
|
||||
return managedObjectContext;
|
||||
}
|
||||
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
|
||||
|
||||
// Start loading the store.
|
||||
[self storeManager];
|
||||
|
||||
// Wait until the storeManager is ready.
|
||||
while (![self storeManager].isReady)
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
|
||||
return [self storeManager].persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
- (UbiquityStoreManager *)storeManager {
|
||||
|
||||
static UbiquityStoreManager *storeManager = nil;
|
||||
@@ -67,11 +59,11 @@
|
||||
return storeManager;
|
||||
|
||||
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
#if TARGET_OS_IPHONE
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSPersistentStoreFileProtectionKey]
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSPersistentStoreFileProtectionKey]
|
||||
#else
|
||||
additionalStoreOptions:nil
|
||||
#endif
|
||||
@@ -83,9 +75,9 @@
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
@@ -96,9 +88,9 @@
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
@@ -112,10 +104,10 @@
|
||||
|
||||
- (void)saveContext {
|
||||
|
||||
[self.managedObjectContext performBlock:^{
|
||||
[self.managedObjectContextIfReady performBlock:^{
|
||||
NSError *error = nil;
|
||||
if ([self.managedObjectContext hasChanges])
|
||||
if (![self.managedObjectContext save:&error])
|
||||
if ([self.managedObjectContextIfReady hasChanges])
|
||||
if (![self.managedObjectContextIfReady save:&error])
|
||||
err(@"While saving context: %@", error);
|
||||
}];
|
||||
}
|
||||
@@ -124,7 +116,7 @@
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
|
||||
|
||||
return self.managedObjectContext;
|
||||
return self.managedObjectContextIfReady;
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
|
||||
@@ -142,7 +134,8 @@
|
||||
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
|
||||
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
|
||||
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO"
|
||||
forKey:@"enabled"]];
|
||||
|
||||
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
|
||||
}
|
||||
@@ -168,13 +161,11 @@
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetLocalStorage];
|
||||
|
||||
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
|
||||
return;
|
||||
Throw(@"Local store was reset, application must be restarted to use it.");
|
||||
}
|
||||
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
|
||||
wrn(@"iCloud store could not be opened, resetting it.");
|
||||
@@ -182,8 +173,7 @@
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetCloudStorage];
|
||||
break;
|
||||
@@ -199,28 +189,26 @@
|
||||
inf(@"Importing sites.");
|
||||
|
||||
static NSRegularExpression *headerPattern, *sitePattern;
|
||||
__autoreleasing NSError *error;
|
||||
__block NSError *error = nil;
|
||||
if (!headerPattern) {
|
||||
headerPattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
headerPattern = [[NSRegularExpression alloc] initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the header pattern: %@", error);
|
||||
}
|
||||
if (!sitePattern) {
|
||||
sitePattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
sitePattern = [[NSRegularExpression alloc] initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the site pattern: %@", error);
|
||||
}
|
||||
if (!headerPattern || !sitePattern)
|
||||
return MPImportResultInternalError;
|
||||
|
||||
NSData *key = nil;
|
||||
NSString *keyIDHex = nil, *userName = nil;
|
||||
MPUserEntity *user = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
MPKey *key = nil;
|
||||
__block MPUserEntity *user = nil;
|
||||
NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
||||
@@ -247,20 +235,33 @@
|
||||
}
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
|
||||
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([headerName isEqualToString:@"User Name"]) {
|
||||
userName = headerValue;
|
||||
key = keyForPassword(password, userName);
|
||||
|
||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
|
||||
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
|
||||
}
|
||||
if ([headerName isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
__block NSArray *users = nil;
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
users = [self.managedObjectContextIfReady executeFetchRequest:userFetchRequest error:&error];
|
||||
}];
|
||||
if (!users) {
|
||||
err(@"While looking for user: %@, error: %@", userName, error);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
if ([users count] > 1) {
|
||||
err(@"While looking for user: %@, found more than one: %u", userName, [users count]);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
|
||||
user = [users count]? [users lastObject]: nil;
|
||||
dbg(@"Found user: %@", [user debugDescription]);
|
||||
}
|
||||
if ([headerName isEqualToString:@"Key ID"])
|
||||
keyIDHex = headerValue;
|
||||
if ([headerName isEqualToString:@"Version"])
|
||||
bundleVersion = headerValue;
|
||||
if ([headerName isEqualToString:@"Passwords"]) {
|
||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||
clearText = YES;
|
||||
@@ -272,6 +273,11 @@
|
||||
continue;
|
||||
if (!keyIDHex || ![userName length])
|
||||
return MPImportResultMalformedInput;
|
||||
if (!key) {
|
||||
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
|
||||
if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
}
|
||||
if (![importedSiteLine length])
|
||||
continue;
|
||||
|
||||
@@ -285,22 +291,26 @@
|
||||
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
||||
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
||||
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
||||
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
|
||||
|
||||
// Find existing site.
|
||||
if (user) {
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
|
||||
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
err(@"Couldn't search existing sites: %@", error);
|
||||
__block NSArray *existingSites = nil;
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
existingSites = [self.managedObjectContextIfReady executeFetchRequest:fetchRequest error:&error];
|
||||
}];
|
||||
if (!existingSites) {
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
} else
|
||||
if (existingSites.count)
|
||||
dbg(@"Existing sites: %@", 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]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,51 +323,74 @@
|
||||
return MPImportResultCancelled;
|
||||
}
|
||||
|
||||
// Delete existing sites.
|
||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
||||
[self.managedObjectContext deleteObject:obj];
|
||||
}];
|
||||
[self saveContext];
|
||||
BOOL success = NO;
|
||||
[self.managedObjectContextIfReady.undoManager beginUndoGrouping];
|
||||
@try {
|
||||
|
||||
// Import new sites.
|
||||
if (!user) {
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
user.name = userName;
|
||||
user.keyID = [keyIDHex decodeHex];
|
||||
}
|
||||
for (NSArray *siteElements in importedSiteElements) {
|
||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
|
||||
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
|
||||
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
|
||||
NSString *name = [siteElements objectAtIndex:3];
|
||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
||||
// Delete existing sites.
|
||||
if (elementsToDelete.count)
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
||||
dbg(@"Deleted Element: %@", [obj debugDescription]);
|
||||
[self.managedObjectContextIfReady deleteObject:obj];
|
||||
}];
|
||||
}];
|
||||
|
||||
// Create new site.
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
if ([exportContent length])
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:key];
|
||||
else
|
||||
[element importProtectedContent:exportContent];
|
||||
}
|
||||
[self saveContext];
|
||||
// Import new sites.
|
||||
if (!user) {
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:self.managedObjectContextIfReady];
|
||||
user.name = userName;
|
||||
user.keyID = [keyIDHex decodeHex];
|
||||
}];
|
||||
dbg(@"Created User: %@", [user debugDescription]);
|
||||
}
|
||||
for (NSArray *siteElements in importedSiteElements) {
|
||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
|
||||
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
|
||||
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
|
||||
NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue];
|
||||
NSString *name = [siteElements objectAtIndex:4];
|
||||
NSString *exportContent = [siteElements objectAtIndex:5];
|
||||
|
||||
inf(@"Import completed successfully.");
|
||||
// Create new site.
|
||||
[self.managedObjectContextIfReady performBlockAndWait:^{
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
|
||||
inManagedObjectContext:self.managedObjectContextIfReady];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
element.version = version;
|
||||
if ([exportContent length]) {
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:key];
|
||||
else
|
||||
[element importProtectedContent:exportContent];
|
||||
}
|
||||
dbg(@"Created Element: %@", [element debugDescription]);
|
||||
}];
|
||||
}
|
||||
|
||||
[self saveContext];
|
||||
success = YES;
|
||||
inf(@"Import completed successfully.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesImported];
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesImported];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil];
|
||||
|
||||
return MPImportResultSuccess;
|
||||
return MPImportResultSuccess;
|
||||
}
|
||||
@finally {
|
||||
[self.managedObjectContextIfReady.undoManager endUndoGrouping];
|
||||
|
||||
if (!success)
|
||||
[self.managedObjectContextIfReady.undoManager undoNestedGroup];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
||||
@@ -389,8 +422,9 @@
|
||||
// Sites.
|
||||
for (MPElementEntity *element in self.activeUser.elements) {
|
||||
NSDate *lastUsed = element.lastUsed;
|
||||
NSUInteger uses = element.uses;
|
||||
MPElementType type = element.type;
|
||||
NSUInteger uses = element.uses;
|
||||
MPElementType type = element.type;
|
||||
NSUInteger version = element.version;
|
||||
NSString *name = element.name;
|
||||
NSString *content = nil;
|
||||
|
||||
@@ -403,8 +437,9 @@
|
||||
content = element.exportContent;
|
||||
}
|
||||
|
||||
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
|
||||
[export appendFormat:@"%@ %8d %8s %20s\t%@\n",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses,
|
||||
[PearlString(@"%u:%u", type, version) UTF8String], [name UTF8String], content
|
||||
? content: @""];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
@property (nonatomic, retain) id content;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
|
||||
@property (nonatomic, retain) NSNumber * type_;
|
||||
@property (nonatomic, retain) NSString * userName;
|
||||
@property (nonatomic, retain) NSNumber * uses_;
|
||||
@property (nonatomic, retain) NSNumber * version_;
|
||||
@property (nonatomic, retain) MPUserEntity *user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
@dynamic content;
|
||||
@dynamic lastUsed;
|
||||
@dynamic name;
|
||||
@dynamic requiresExplicitMigration_;
|
||||
@dynamic type_;
|
||||
@dynamic userName;
|
||||
@dynamic uses_;
|
||||
@dynamic version_;
|
||||
@dynamic user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementGeneratedEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementGeneratedEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementStoredEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPElementStoredEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@@ -11,18 +11,27 @@
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPUserEntity.h"
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
#define MPAvatarCount 19
|
||||
|
||||
@interface MPElementEntity (MP)
|
||||
|
||||
@property (assign) MPElementType type;
|
||||
@property (readonly) NSString *typeName;
|
||||
@property (readonly) NSString *typeShortName;
|
||||
@property (readonly) NSString *typeClassName;
|
||||
@property (readonly) Class typeClass;
|
||||
@property (assign) NSUInteger uses;
|
||||
@property (assign) NSUInteger version;
|
||||
@property (assign) BOOL requiresExplicitMigration;
|
||||
@property (readonly) id<MPAlgorithm> algorithm;
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (NSString *)exportContent;
|
||||
- (void)importProtectedContent:(NSString *)protectedContent;
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
||||
|
||||
@end
|
||||
|
||||
@@ -37,6 +46,7 @@
|
||||
@property (assign) NSUInteger avatar;
|
||||
@property (assign) BOOL saveKey;
|
||||
@property (assign) MPElementType defaultType;
|
||||
@property (assign) BOOL requiresExplicitMigration;
|
||||
@property (readonly) NSString *userID;
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
@implementation MPElementEntity (MP)
|
||||
|
||||
@@ -23,6 +21,26 @@
|
||||
self.type_ = PearlUnsignedInteger(aType);
|
||||
}
|
||||
|
||||
- (NSString *)typeName {
|
||||
|
||||
return [self.algorithm nameOfType:self.type];
|
||||
}
|
||||
|
||||
- (NSString *)typeShortName {
|
||||
|
||||
return [self.algorithm shortNameOfType:self.type];
|
||||
}
|
||||
|
||||
- (NSString *)typeClassName {
|
||||
|
||||
return [self.algorithm classNameOfType:self.type];
|
||||
}
|
||||
|
||||
- (Class)typeClass {
|
||||
|
||||
return [self.algorithm classOfType:self.type];
|
||||
}
|
||||
|
||||
- (NSUInteger)uses {
|
||||
|
||||
return [self.uses_ unsignedIntegerValue];
|
||||
@@ -33,6 +51,30 @@
|
||||
self.uses_ = PearlUnsignedInteger(anUses);
|
||||
}
|
||||
|
||||
- (NSUInteger)version {
|
||||
|
||||
return [self.version_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setVersion:(NSUInteger)version {
|
||||
|
||||
self.version_ = PearlUnsignedInteger(version);
|
||||
}
|
||||
|
||||
- (BOOL)requiresExplicitMigration {
|
||||
|
||||
return [self.requiresExplicitMigration_ boolValue];
|
||||
}
|
||||
|
||||
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
|
||||
|
||||
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (id<MPAlgorithm>)algorithm {
|
||||
|
||||
return MPAlgorithmForVersion(self.version);
|
||||
}
|
||||
|
||||
- (NSUInteger)use {
|
||||
|
||||
@@ -54,8 +96,8 @@
|
||||
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
@@ -65,8 +107,22 @@
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
|
||||
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed);
|
||||
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@, version=%d, userName=%@, requiresExplicitMigration=%d}",
|
||||
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed, self.version,
|
||||
self.userName, self.requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
||||
|
||||
while (self.version < MPAlgorithmDefaultVersion)
|
||||
if ([MPAlgorithmForVersion(self.version + 1) migrateElement:self explicit:explicit])
|
||||
inf(@"%@ migration to version: %d succeeded for element: %@", explicit? @"Explicit": @"Automatic", self.version + 1, self);
|
||||
else {
|
||||
wrn(@"%@ migration to version: %d failed for element: %@", explicit? @"Explicit": @"Automatic", self.version + 1, self);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -85,6 +141,10 @@
|
||||
|
||||
- (id)content {
|
||||
|
||||
MPKey *key = [MPAppDelegate get].key;
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
if (!(self.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
|
||||
return nil;
|
||||
@@ -93,7 +153,7 @@
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
|
||||
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
|
||||
return [self.algorithm generateContentForElement:self usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -107,23 +167,31 @@
|
||||
@"DevicePrivate", (__bridge id)kSecAttrService,
|
||||
name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
return [self contentUsingKey:[MPAppDelegate get].key];
|
||||
|
||||
MPKey *key = [MPAppDelegate get].key;
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
return [self contentUsingKey:key];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
[self setContent:content usingKey:[MPAppDelegate get].key];
|
||||
|
||||
MPKey *key = [MPAppDelegate get].key;
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
[self setContent:content usingKey:key];
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(NSData *)key {
|
||||
- (id)contentUsingKey:(MPKey *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type & MPElementFeatureDevicePrivate)
|
||||
@@ -131,25 +199,29 @@
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length])
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content usingKey:(NSData *)key {
|
||||
- (void)setContent:(id)content usingKey:(MPKey *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
|
||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
|
||||
if (self.type & MPElementFeatureDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
(__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
self.contentObject = nil;
|
||||
} else
|
||||
self.contentObject = encryptedContent;
|
||||
@@ -165,8 +237,8 @@
|
||||
self.contentObject = [protectedContent decodeBase64];
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
||||
|
||||
[self setContent:clearContent usingKey:key];
|
||||
}
|
||||
|
||||
@@ -199,17 +271,26 @@
|
||||
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSString *)userID {
|
||||
|
||||
return [MPUserEntity idFor:self.name];
|
||||
}
|
||||
|
||||
|
||||
- (void)setDefaultType:(MPElementType)aDefaultType {
|
||||
|
||||
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
|
||||
}
|
||||
|
||||
- (BOOL)requiresExplicitMigration {
|
||||
|
||||
return [self.requiresExplicitMigration_ boolValue];
|
||||
}
|
||||
|
||||
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
|
||||
|
||||
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
|
||||
}
|
||||
|
||||
- (NSString *)userID {
|
||||
|
||||
return [MPUserEntity idFor:self.name];
|
||||
}
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName {
|
||||
|
||||
return [[userName hashWith:PearlHashSHA1] encodeHex];
|
||||
|
||||
33
MasterPassword/MPKey.h
Normal file
@@ -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
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPKey
|
||||
//
|
||||
// Created by Maarten Billemont on 16/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPKey.h"
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
|
||||
@interface MPKey ()
|
||||
|
||||
@property (nonatomic, readwrite, strong) id<MPAlgorithm> algorithm;
|
||||
@property (nonatomic, readwrite, strong) NSData *keyData;
|
||||
@property (nonatomic, readwrite, strong) NSData *keyID;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPKey
|
||||
@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID;
|
||||
|
||||
- (id)initWithKeyData:(NSData *)keyData algorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
self.keyData = keyData;
|
||||
self.algorithm = algorithm;
|
||||
self.keyID = [self.algorithm keyIDForKeyData:keyData];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength {
|
||||
|
||||
NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange(0, MIN(subKeyLength, self.keyData.length))];
|
||||
|
||||
return [self.algorithm keyFromKeyData:subKeyData];
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToKey:(MPKey *)key {
|
||||
|
||||
return [self.keyID isEqualToData:key.keyID];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
|
||||
if (![object isKindOfClass:[MPKey class]])
|
||||
return NO;
|
||||
|
||||
return [self isEqualToKey:object];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -6,9 +6,7 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define MPPersistentStoreDidChangeNotification @"MPPersistentStoreDidChange"
|
||||
#import "MPKey.h"
|
||||
|
||||
typedef enum {
|
||||
MPElementContentTypePassword,
|
||||
@@ -45,10 +43,13 @@ typedef enum {
|
||||
#define MPCheckpointAction @"MPCheckpointAction"
|
||||
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
|
||||
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
|
||||
#define MPCheckpointCopyUserNameToPasteboard @"MPCheckpointCopyUserNameToPasteboard"
|
||||
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
|
||||
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
|
||||
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
|
||||
#define MPCheckpointEditUserName @"MPCheckpointEditUserName"
|
||||
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
|
||||
#define MPCheckpointCloseOutdatedAlert @"MPCheckpointCloseOutdatedAlert"
|
||||
#define MPCheckpointUseType @"MPCheckpointUseType"
|
||||
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
|
||||
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
|
||||
@@ -70,19 +71,9 @@ typedef enum {
|
||||
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
|
||||
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
|
||||
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
|
||||
#define MPCheckpointExplicitMigration @"MPCheckpointExplicitMigration"
|
||||
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationSignedIn @"MPNotificationKeySet"
|
||||
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
|
||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||
|
||||
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);
|
||||
#define MPNotificationElementUpdated @"MPNotificationElementUpdated"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPUserEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
@interface MPUserEntity : NSManagedObject
|
||||
|
||||
@property (nonatomic, retain) NSNumber * avatar_;
|
||||
@property (nonatomic, retain) NSNumber * defaultType_;
|
||||
@property (nonatomic, retain) NSData * keyID;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * saveKey_;
|
||||
@property (nonatomic, retain) NSNumber * defaultType_;
|
||||
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
|
||||
@property (nonatomic, retain) NSSet *elements;
|
||||
@end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MPUserEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
@implementation MPUserEntity
|
||||
|
||||
@dynamic avatar_;
|
||||
@dynamic defaultType_;
|
||||
@dynamic keyID;
|
||||
@dynamic lastUsed;
|
||||
@dynamic name;
|
||||
@dynamic saveKey_;
|
||||
@dynamic defaultType_;
|
||||
@dynamic requiresExplicitMigration_;
|
||||
@dynamic elements;
|
||||
|
||||
@end
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MasterPassword.xcdatamodel</string>
|
||||
<string>MasterPassword 2.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1487" systemVersion="12A269" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||
<userInfo/>
|
||||
</attribute>
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="180"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -18,6 +18,6 @@
|
||||
- (void)showGuide;
|
||||
|
||||
- (void)export;
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user;
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user didResetBlock:(void(^)(void))didReset;
|
||||
|
||||
@end
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
[MPiOSConfig get];
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
|
||||
[PearlLogger get].printLevel = PearlLogLevelDebug;
|
||||
//[NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
|
||||
#endif
|
||||
}
|
||||
@@ -49,16 +49,14 @@
|
||||
[[[NSBundle mainBundle] mutableInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
[[[NSBundle mainBundle] mutableLocalizedInfoDictionary] setObject:@"Master Password" forKey:@"CFBundleDisplayName"];
|
||||
|
||||
#ifdef ADHOC
|
||||
@try {
|
||||
NSString *testFlightToken = [self testFlightToken];
|
||||
if ([testFlightToken length]) {
|
||||
inf(@"Initializing TestFlight");
|
||||
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
|
||||
#ifdef ADHOC
|
||||
[TestFlight setDeviceIdentifier:[(id)[UIDevice currentDevice] uniqueIdentifier]];
|
||||
#else
|
||||
[TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
#endif
|
||||
// [TestFlight setDeviceIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
@@ -74,11 +72,14 @@
|
||||
|
||||
return YES;
|
||||
}];
|
||||
TFLog(@"TestFlight (%@) initialized for: %@ v%@.", //
|
||||
TESTFLIGHT_SDK_VERSION, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion);
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
#endif
|
||||
@try {
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
@@ -99,6 +100,8 @@
|
||||
|
||||
return YES;
|
||||
}];
|
||||
CLSLog(@"Crashlytics (%@) initialized for: %@ v%@.", //
|
||||
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion);
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
@@ -111,13 +114,13 @@
|
||||
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelWarn)
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[message levelDescription],
|
||||
@"level",
|
||||
message.message,
|
||||
@"message",
|
||||
nil]];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem"
|
||||
attributes:[NSDictionary
|
||||
dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithCString:PearlLogLevelStr(message.level)
|
||||
encoding:NSASCIIStringEncoding], @"level",
|
||||
message.message, @"message",
|
||||
nil]];
|
||||
|
||||
return YES;
|
||||
}];
|
||||
@@ -151,7 +154,7 @@
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
|
||||
nil]
|
||||
forState:UIControlStateNormal];
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
|
||||
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
|
||||
@@ -182,7 +185,11 @@
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
|
||||
if ([[note.userInfo objectForKey:@"animated"] boolValue])
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
|
||||
else
|
||||
[self.navigationController presentViewController:[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:NO completion:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
@@ -216,7 +223,7 @@
|
||||
__autoreleasing NSError *error;
|
||||
__autoreleasing NSURLResponse *response;
|
||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||
returningResponse:&response error:&error];
|
||||
returningResponse:&response error:&error];
|
||||
if (error)
|
||||
err(@"While reading imported sites from %@: %@", url, error);
|
||||
if (!importedSitesData)
|
||||
@@ -229,33 +236,33 @@
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
|
||||
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block BOOL confirmation = NO;
|
||||
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block BOOL confirmation = NO;
|
||||
|
||||
dispatch_group_t confirmationGroup = dispatch_group_create();
|
||||
dispatch_group_enter(confirmationGroup);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:@"Import Sites?"
|
||||
message:PearlString(
|
||||
@"Import %d sites, overwriting %d existing sites?",
|
||||
importCount, deleteCount)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_
|
||||
!= [alert_ cancelButtonIndex])
|
||||
confirmation = YES;
|
||||
dispatch_group_t confirmationGroup = dispatch_group_create();
|
||||
dispatch_group_enter(confirmationGroup);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:@"Import Sites?"
|
||||
message:PearlString(
|
||||
@"Import %d sites, overwriting %d existing sites?",
|
||||
importCount, deleteCount)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_
|
||||
!= [alert_ cancelButtonIndex])
|
||||
confirmation = YES;
|
||||
|
||||
dispatch_group_leave(confirmationGroup);
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:@"Import", nil];
|
||||
});
|
||||
dispatch_group_wait(
|
||||
confirmationGroup, DISPATCH_TIME_FOREVER);
|
||||
dispatch_group_leave(confirmationGroup);
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:@"Import", nil];
|
||||
});
|
||||
dispatch_group_wait(
|
||||
confirmationGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return confirmation;
|
||||
}];
|
||||
return confirmation;
|
||||
}];
|
||||
|
||||
switch (result) {
|
||||
case MPImportResultSuccess:
|
||||
@@ -273,19 +280,23 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
||||
|
||||
wrn(@"Received memory warning.");
|
||||
|
||||
[super applicationDidReceiveMemoryWarning:application];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Re-activated");
|
||||
[[MPAppDelegate get] checkConfig];
|
||||
|
||||
if ([[MPiOSConfig get].showQuickStart boolValue])
|
||||
[self showGuide];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointActivated];
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
@@ -325,7 +336,7 @@
|
||||
[self saveContext];
|
||||
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOut];
|
||||
[self signOutAnimated:NO];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointDeactivated];
|
||||
}
|
||||
@@ -337,8 +348,8 @@
|
||||
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
|
||||
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
if ([PearlLogger get].autoprintLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelInfo;
|
||||
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].printLevel = PearlLogLevelInfo;
|
||||
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].iCloud boolValue] forKey:@"iCloud"];
|
||||
@@ -381,11 +392,14 @@
|
||||
? @"YES": @"NO", @"showQuickStart",
|
||||
[[PearlConfig get].firstRun boolValue]
|
||||
? @"YES": @"NO", @"firstRun",
|
||||
[[PearlConfig get].launchCount description], @"launchCount",
|
||||
[[PearlConfig get].launchCount description],
|
||||
@"launchCount",
|
||||
[[PearlConfig get].askForReviews boolValue]
|
||||
? @"YES": @"NO", @"askForReviews",
|
||||
[[PearlConfig get].reviewAfterLaunches description], @"reviewAfterLaunches",
|
||||
[PearlConfig get].reviewedVersion, @"reviewedVersion",
|
||||
[[PearlConfig get].reviewAfterLaunches description],
|
||||
@"reviewAfterLaunches",
|
||||
[PearlConfig get].reviewedVersion,
|
||||
@"reviewedVersion",
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
@@ -418,20 +432,19 @@
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
// Show Passwords
|
||||
[self exportShowPasswords:YES];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
}
|
||||
|
||||
- (void)exportShowPasswords:(BOOL)showPasswords {
|
||||
|
||||
|
||||
if (![MFMailComposeViewController canSendMail]) {
|
||||
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
|
||||
message:
|
||||
@"Your device is not yet set up for sending mail.\n"
|
||||
@"Close Master Password, go into Settings and add a Mail account."
|
||||
@"Your device is not yet set up for sending mail.\n"
|
||||
@"Close Master Password, go into Settings and add a Mail account."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil
|
||||
cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
@@ -441,24 +454,24 @@
|
||||
|
||||
if (showPasswords)
|
||||
message = PearlString(@"Export of Master Password sites with passwords included.\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
else
|
||||
message = PearlString(@"Backup of Master Password sites.\n\n\n"
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
@"--\n"
|
||||
@"%@\n"
|
||||
@"Master Password %@, build %@",
|
||||
self.activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion);
|
||||
|
||||
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
||||
|
||||
MFMailComposeViewController *composer = [MFMailComposeViewController new];
|
||||
[composer setMailComposeDelegate:self];
|
||||
@@ -466,13 +479,13 @@
|
||||
[composer setMessageBody:message isHTML:NO];
|
||||
[composer addAttachmentData:
|
||||
[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@ (%@).mpsites",
|
||||
self.activeUser.name,
|
||||
[exportDateFormatter stringFromDate:[NSDate date]])];
|
||||
fileName:PearlString(@"%@ (%@).mpsites",
|
||||
self.activeUser.name,
|
||||
[exportDateFormatter stringFromDate:[NSDate date]])];
|
||||
[self.window.rootViewController presentModalViewController:composer animated:YES];
|
||||
}
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user {
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user didResetBlock:(void (^)(void))didReset {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@@ -487,14 +500,16 @@
|
||||
inf(@"Unsetting master password for: %@.", user.userID);
|
||||
user.keyID = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
[self signOut];
|
||||
[self signOutAnimated:YES];
|
||||
|
||||
if (didReset)
|
||||
didReset();
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointChangeMP];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP
|
||||
attributes:nil];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointChangeMP attributes:nil];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
#pragma mark - PearlConfigDelegate
|
||||
@@ -522,7 +537,7 @@
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
return;
|
||||
} otherTitles:@"Retry", nil];
|
||||
} otherTitles:@"Retry", nil];
|
||||
return;
|
||||
case MFMailComposeResultCancelled:
|
||||
break;
|
||||
@@ -563,7 +578,7 @@
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
[self ubiquityStoreManager:manager didSwitchToiCloud:iCloudEnabled];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonThanks otherTitles:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -572,7 +587,7 @@
|
||||
return;
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[manager useiCloudStore:YES alertUser:NO];
|
||||
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
|
||||
} cancelTitle:@"Leave iCloud Off" otherTitles:@"Explain?", @"Enable iCloud", nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPSearchDelegate.h"
|
||||
|
||||
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate>
|
||||
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate, UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, assign) BOOL userNameHidden;
|
||||
@property (strong, nonatomic) MPElementEntity *activeElement;
|
||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
|
||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchDelegate;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *contentField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
|
||||
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
|
||||
@@ -22,29 +23,47 @@
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordUpgrade;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *displayContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *userNameTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *userNameTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *toolTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
|
||||
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *resetPasswordCounterGesture;
|
||||
@property (weak, nonatomic) IBOutlet UIView *toolTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *toolTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIView *userNameContainer;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordUser;
|
||||
@property (weak, nonatomic) IBOutlet UIView *outdatedAlertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *outdatedAlertBack;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *outdatedAlertCloseButton;
|
||||
|
||||
|
||||
@property (copy) void (^contentTipCleanup)(BOOL finished);
|
||||
@property (copy) void (^toolTipCleanup)(BOOL finished);
|
||||
|
||||
- (IBAction)copyContent;
|
||||
- (IBAction)incrementPasswordCounter;
|
||||
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
|
||||
- (IBAction)editUserName:(UILongPressGestureRecognizer *)sender;
|
||||
- (IBAction)editPassword;
|
||||
- (IBAction)closeAlert;
|
||||
- (IBAction)upgradePassword;
|
||||
- (IBAction)action:(UIBarButtonItem *)sender;
|
||||
- (IBAction)toggleUser;
|
||||
- (IBAction)searchOutdatedElements;
|
||||
- (IBAction)closeOutdatedAlert;
|
||||
- (IBAction)infoOutdatedAlert;
|
||||
|
||||
- (BOOL)isHelpVisible;
|
||||
- (void)toggleHelpAnimated:(BOOL)animated;
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated;
|
||||
- (void)setHelpChapter:(NSString *)chapter;
|
||||
|
||||
@@ -13,39 +13,41 @@
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
|
||||
@interface MPMainViewController (Private)
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated;
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMainViewController
|
||||
@synthesize userNameHidden = _userNameHidden;
|
||||
@synthesize activeElement = _activeElement;
|
||||
@synthesize searchResultsController = _searchResultsController;
|
||||
@synthesize searchDelegate = _searchDelegate;
|
||||
@synthesize typeButton = _typeButton;
|
||||
@synthesize helpView = _helpView;
|
||||
@synthesize siteName = _siteName;
|
||||
@synthesize passwordCounter = _passwordCounter;
|
||||
@synthesize passwordIncrementer = _passwordIncrementer;
|
||||
@synthesize passwordEdit = _passwordEdit;
|
||||
@synthesize passwordUpgrade = _passwordUpgrade;
|
||||
@synthesize contentContainer = _contentContainer;
|
||||
@synthesize displayContainer = _displayContainer;
|
||||
@synthesize helpContainer = _helpContainer;
|
||||
@synthesize contentTipContainer = _copiedContainer;
|
||||
@synthesize userNameTipContainer = _userNameTipContainer;
|
||||
@synthesize alertContainer = _alertContainer;
|
||||
@synthesize alertTitle = _alertTitle;
|
||||
@synthesize alertBody = _alertBody;
|
||||
@synthesize contentTipBody = _contentTipBody;
|
||||
@synthesize contentTipEditIcon = _contentTipEditIcon;
|
||||
@synthesize userNameTipBody = _userNameTipBody;
|
||||
@synthesize toolTipEditIcon = _contentTipEditIcon;
|
||||
@synthesize searchTipContainer = _searchTipContainer;
|
||||
@synthesize actionsTipContainer = _actionsTipContainer;
|
||||
@synthesize typeTipContainer = _typeTipContainer;
|
||||
@synthesize resetPasswordCounterGesture = _resetPasswordCounterGesture;
|
||||
@synthesize toolTipContainer = _toolTipContainer;
|
||||
@synthesize toolTipBody = _toolTipBody;
|
||||
@synthesize userNameContainer = _userNameContainer;
|
||||
@synthesize userNameField = _userNameField;
|
||||
@synthesize passwordUser = _passwordUser;
|
||||
@synthesize outdatedAlertContainer = _outdatedAlertContainer;
|
||||
@synthesize outdatedAlertBack = _outdatedAlertBack;
|
||||
@synthesize outdatedAlertCloseButton = _outdatedAlertCloseButton;
|
||||
@synthesize contentField = _contentField;
|
||||
@synthesize contentTipCleanup;
|
||||
@synthesize contentTipCleanup = _contentTipCleanup, toolTipCleanup = _toolTipCleanup;
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
@@ -56,7 +58,8 @@
|
||||
|
||||
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
||||
|
||||
[self setHelpHidden:![self isHelpVisible] animated:NO];
|
||||
[self updateHelpHiddenAnimated:NO];
|
||||
[self updateUserHiddenAnimated:NO];
|
||||
}
|
||||
|
||||
|
||||
@@ -68,15 +71,46 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.resetPasswordCounterGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resetPasswordCounter:)];
|
||||
[self.passwordIncrementer addGestureRecognizer:self.resetPasswordCounterGesture];
|
||||
|
||||
self.searchDelegate = [MPSearchDelegate new];
|
||||
self.searchDelegate.delegate = self;
|
||||
self.searchDelegate.searchDisplayController = self.searchDisplayController;
|
||||
self.searchDelegate.searchTipContainer = self.searchTipContainer;
|
||||
self.searchDisplayController.searchBar.delegate = self.searchDelegate;
|
||||
self.searchDisplayController.delegate = self.searchDelegate;
|
||||
self.searchDisplayController.searchResultsDelegate = self.searchDelegate;
|
||||
self.searchDisplayController.searchResultsDataSource = self.searchDelegate;
|
||||
|
||||
[self.passwordIncrementer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(resetPasswordCounter:)]];
|
||||
[self.userNameContainer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(editUserName:)]];
|
||||
[self.userNameContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(copyUserName:)]];
|
||||
[self.outdatedAlertBack addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(infoOutdatedAlert)]];
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
|
||||
|
||||
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
|
||||
|
||||
self.alertBody.text = nil;
|
||||
self.contentTipEditIcon.hidden = YES;
|
||||
self.alertBody.text = nil;
|
||||
self.toolTipEditIcon.hidden = YES;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[MPAppDelegate get].activeUser.requiresExplicitMigration = NO;
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationElementUpdated object:nil queue:nil
|
||||
usingBlock:^void(NSNotification *note) {
|
||||
if (self.activeElement.type & MPElementTypeClassStored
|
||||
&& ![[self.activeElement.content description] length])
|
||||
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
|
||||
if (self.activeElement.requiresExplicitMigration)
|
||||
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
|
||||
usingBlock:^void(NSNotification *note) {
|
||||
self.activeElement = nil;
|
||||
}];
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
@@ -86,18 +120,20 @@
|
||||
inf(@"Main will appear.");
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
if (![MPAppDelegate get].activeUser)
|
||||
[self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
if ([[MPiOSConfig get].showQuickStart boolValue])
|
||||
[[MPAppDelegate get] showGuide];
|
||||
|
||||
if (self.activeElement.user != [MPAppDelegate get].activeUser)
|
||||
self.activeElement = nil;
|
||||
self.searchDisplayController.searchBar.text = nil;
|
||||
|
||||
self.searchTipContainer.alpha = 0;
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
self.typeTipContainer.alpha = 0;
|
||||
self.alertContainer.alpha = 0;
|
||||
self.outdatedAlertContainer.alpha = 0;
|
||||
self.searchTipContainer.alpha = 0;
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
self.typeTipContainer.alpha = 0;
|
||||
self.toolTipContainer.alpha = 0;
|
||||
|
||||
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
|
||||
[self updateAnimated:animated];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
@@ -105,11 +141,17 @@
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
if (![MPAppDelegate get].activeUser)
|
||||
[self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
|
||||
if (![[MPiOSConfig get].actionsTipShown boolValue])
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.actionsTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[MPiOSConfig get].actionsTipShown = PearlBool(YES);
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.actionsTipContainer.alpha = 0;
|
||||
@@ -123,6 +165,33 @@
|
||||
}
|
||||
}];
|
||||
|
||||
if ([MPAppDelegate get].activeUser)
|
||||
[[MPAppDelegate get].managedObjectContextIfReady performBlock:^void() {
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *migrationRequest = [NSFetchRequest
|
||||
fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d", MPAlgorithmDefaultVersion];
|
||||
NSArray *migrationElements = [[MPAppDelegate get].managedObjectContextIfReady 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 viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@@ -136,12 +205,12 @@
|
||||
|
||||
[self setContentField:nil];
|
||||
[self setTypeButton:nil];
|
||||
[self setSearchResultsController:nil];
|
||||
[self setHelpView:nil];
|
||||
[self setSiteName:nil];
|
||||
[self setPasswordCounter:nil];
|
||||
[self setPasswordIncrementer:nil];
|
||||
[self setPasswordEdit:nil];
|
||||
[self setPasswordUpgrade:nil];
|
||||
[self setContentContainer:nil];
|
||||
[self setHelpContainer:nil];
|
||||
[self setContentTipContainer:nil];
|
||||
@@ -149,11 +218,21 @@
|
||||
[self setAlertTitle:nil];
|
||||
[self setAlertBody:nil];
|
||||
[self setContentTipBody:nil];
|
||||
[self setContentTipEditIcon:nil];
|
||||
[self setToolTipEditIcon:nil];
|
||||
[self setSearchTipContainer:nil];
|
||||
[self setActionsTipContainer:nil];
|
||||
[self setTypeTipContainer:nil];
|
||||
[self setResetPasswordCounterGesture:nil];
|
||||
[self setToolTipContainer:nil];
|
||||
[self setToolTipBody:nil];
|
||||
[self setDisplayContainer:nil];
|
||||
[self setUserNameField:nil];
|
||||
[self setUserNameTipContainer:nil];
|
||||
[self setUserNameTipBody:nil];
|
||||
[self setUserNameContainer:nil];
|
||||
[self setPasswordUser:nil];
|
||||
[self setOutdatedAlertContainer:nil];
|
||||
[self setOutdatedAlertCloseButton:nil];
|
||||
[self setOutdatedAlertBack:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
@@ -167,22 +246,40 @@
|
||||
}
|
||||
|
||||
[self setHelpChapter:self.activeElement? @"2": @"1"];
|
||||
[self updateHelpHiddenAnimated:NO];
|
||||
|
||||
self.passwordCounter.alpha = 0;
|
||||
self.passwordIncrementer.alpha = 0;
|
||||
self.passwordEdit.alpha = 0;
|
||||
self.passwordUpgrade.alpha = 0;
|
||||
self.passwordUser.alpha = 0;
|
||||
|
||||
if (self.activeElement)
|
||||
self.passwordUser.alpha = 0.5f;
|
||||
|
||||
if (self.activeElement.requiresExplicitMigration)
|
||||
self.passwordUpgrade.alpha = 0.5f;
|
||||
|
||||
else {
|
||||
if (self.activeElement.type & MPElementTypeClassGenerated) {
|
||||
self.passwordCounter.alpha = 0.5f;
|
||||
self.passwordIncrementer.alpha = 0.5f;
|
||||
} else
|
||||
if (self.activeElement.type & MPElementTypeClassStored)
|
||||
self.passwordEdit.alpha = 0.5f;
|
||||
}
|
||||
|
||||
self.siteName.text = self.activeElement.name;
|
||||
|
||||
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
||||
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
|
||||
|
||||
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
|
||||
self.typeButton.alpha = self.activeElement? 1: 0;
|
||||
[self.typeButton setTitle:self.activeElement.typeName
|
||||
forState:UIControlStateNormal];
|
||||
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
|
||||
|
||||
self.contentField.enabled = NO;
|
||||
|
||||
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
self.passwordCounter.text = PearlString(@"%u", ((MPElementGeneratedEntity *)self.activeElement).counter);
|
||||
|
||||
self.contentField.text = @"";
|
||||
self.contentField.enabled = NO;
|
||||
self.contentField.text = @"";
|
||||
if (self.activeElement.name && ![self.activeElement isDeleted])
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSString *description = [self.activeElement.content description];
|
||||
@@ -191,33 +288,71 @@
|
||||
self.contentField.text = description;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isHelpVisible {
|
||||
|
||||
return self.helpContainer.frame.origin.y == 216;
|
||||
self.userNameField.enabled = NO;
|
||||
self.userNameField.text = self.activeElement.userName;
|
||||
self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName
|
||||
== nil));
|
||||
[self updateUserHiddenAnimated:NO];
|
||||
}
|
||||
|
||||
- (void)toggleHelpAnimated:(BOOL)animated {
|
||||
|
||||
[self setHelpHidden:[self isHelpVisible] animated:animated];
|
||||
[self setHelpHidden:![[MPiOSConfig get].helpHidden boolValue] animated:animated];
|
||||
}
|
||||
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
if (hidden) {
|
||||
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);
|
||||
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:YES];
|
||||
} else {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
|
||||
[MPiOSConfig get].helpHidden = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
[MPiOSConfig get].helpHidden = PearlBool(hidden);
|
||||
[self updateHelpHiddenAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)updateHelpHiddenAnimated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateHelpHiddenAnimated:NO];
|
||||
}];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ([[MPiOSConfig get].helpHidden boolValue]) {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, self.view.bounds.size.height - 44 /* search bar */);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame,
|
||||
self.view.bounds.size.height + 20 /* view moves up a bit when search appears. */);
|
||||
} else {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 225);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 266);
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleUser {
|
||||
|
||||
[self toggleUserAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)toggleUserAnimated:(BOOL)animated {
|
||||
|
||||
[MPiOSConfig get].userNameHidden = PearlBool(!self.userNameHidden);
|
||||
self.userNameHidden = [[MPiOSConfig get].userNameHidden boolValue];
|
||||
[self updateUserHiddenAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)updateUserHiddenAnimated:(BOOL)animated {
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateUserHiddenAnimated:NO];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.userNameHidden) {
|
||||
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87);
|
||||
} else {
|
||||
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 137);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)setHelpChapter:(NSString *)chapter {
|
||||
@@ -234,7 +369,7 @@
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
|
||||
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
|
||||
PearlString(@"setClass('%@');", self.activeElement.typeClassName)];
|
||||
if (error.length)
|
||||
err(@"helpView.setClass: %@", error);
|
||||
}
|
||||
@@ -267,6 +402,54 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showUserNameTip:(NSString *)message {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.userNameTipBody.text = message;
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.userNameTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.userNameTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showToolTip:(NSString *)message withIcon:(UIImageView *)icon {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.toolTipCleanup)
|
||||
self.toolTipCleanup(NO);
|
||||
|
||||
self.toolTipBody.text = message;
|
||||
self.toolTipCleanup = ^(BOOL finished) {
|
||||
icon.hidden = YES;
|
||||
self.toolTipCleanup = nil;
|
||||
};
|
||||
|
||||
icon.hidden = NO;
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.toolTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.toolTipContainer.alpha = 0;
|
||||
} completion:self.toolTipCleanup];
|
||||
});
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@@ -299,7 +482,28 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointCopyToPasteboard];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyToPasteboard
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
self.activeElement.typeName, @"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
- (IBAction)copyUserName:(UITapGestureRecognizer *)sender {
|
||||
|
||||
if (!self.activeElement.userName)
|
||||
return;
|
||||
|
||||
inf(@"Copying user name for: %@", self.activeElement.name);
|
||||
[UIPasteboard generalPasteboard].string = [self.activeElement.content description];
|
||||
|
||||
[self showUserNameTip:@"Copied!"];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCopyUserNameToPasteboard];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyUserNameToPasteboard
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.activeElement.typeName, @"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
@@ -321,7 +525,10 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
@@ -349,11 +556,35 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(self.activeElement.type), @"type",
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)editUserName:(UILongPressGestureRecognizer *)sender {
|
||||
|
||||
if (sender.state != UIGestureRecognizerStateBegan)
|
||||
// Only fire when the gesture was first detected.
|
||||
return;
|
||||
|
||||
if (!self.activeElement)
|
||||
return;
|
||||
|
||||
self.userNameField.enabled = YES;
|
||||
[self.userNameField becomeFirstResponder];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointEditUserName];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditUserName attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
|
||||
@@ -362,7 +593,7 @@
|
||||
return;
|
||||
|
||||
[self changeElementWithoutWarningDo:task];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
|
||||
@@ -394,12 +625,45 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointEditPassword];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(
|
||||
self.activeElement.type), @"type",
|
||||
self.activeElement.typeName, @"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)upgradePassword {
|
||||
|
||||
[self changeElementWithWarning:
|
||||
self.activeElement.type & MPElementTypeClassGenerated?
|
||||
@"You are upgrading the site.\n\n"
|
||||
@"This upgrade improves the site's compatibility with the latest version of Master Password.\n\n"
|
||||
@"Your password will change and you will need to update your site's account."
|
||||
:
|
||||
@"You are upgrading the site.\n\n"
|
||||
@"This upgrade improves the site's compatibility with the latest version of Master Password."
|
||||
do:^{
|
||||
inf(@"Explicitly migrating element: %@", self.activeElement);
|
||||
[self.activeElement migrateExplicitly:YES];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointExplicitMigration];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)searchOutdatedElements {
|
||||
|
||||
self.searchDisplayController.searchBar.selectedScopeButtonIndex = MPSearchScopeOutdated;
|
||||
self.searchDisplayController.searchBar.searchResultsButtonSelected = YES;
|
||||
[self.searchDisplayController.searchBar becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (IBAction)closeAlert {
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
@@ -412,9 +676,27 @@
|
||||
[TestFlight passCheckpoint:MPCheckpointCloseAlert];
|
||||
}
|
||||
|
||||
- (IBAction)closeOutdatedAlert {
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.outdatedAlertContainer.alpha = 0;
|
||||
}];
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointCloseOutdatedAlert];
|
||||
}
|
||||
|
||||
- (IBAction)infoOutdatedAlert {
|
||||
|
||||
[self setHelpChapter:@"outdated"];
|
||||
[self setHelpHidden:NO animated:YES];
|
||||
[self closeOutdatedAlert];
|
||||
[MPAppDelegate get].activeUser.requiresExplicitMigration = NO;
|
||||
}
|
||||
|
||||
- (IBAction)action:(id)sender {
|
||||
|
||||
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
|
||||
initSheet:nil
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
@@ -459,7 +741,7 @@
|
||||
@"masterpassword@lyndir.com"
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
|
||||
otherTitles:nil];
|
||||
otherTitles:nil];
|
||||
|
||||
else {
|
||||
[PearlAlert showAlertWithTitle:@"Sending Feedback"
|
||||
@@ -472,7 +754,8 @@
|
||||
MFMailComposeViewController *composer = [MFMailComposeViewController new];
|
||||
[composer setMailComposeDelegate:self];
|
||||
[composer setToRecipients:[NSArray arrayWithObject:@"Master Password Development <masterpassword@lyndir.com>"]];
|
||||
[composer setSubject:PearlString(@"Feedback for Master Password [%@]", [[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
|
||||
[composer setSubject:PearlString(@"Feedback for Master Password [%@]",
|
||||
[[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
|
||||
[composer setMessageBody:
|
||||
PearlString(
|
||||
@"\n\n\n"
|
||||
@@ -482,20 +765,21 @@
|
||||
[MPAppDelegate get].activeUser.name,
|
||||
[PearlInfoPlist get].CFBundleShortVersionString,
|
||||
[PearlInfoPlist get].CFBundleVersion)
|
||||
isHTML:NO];
|
||||
isHTML:NO];
|
||||
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex]) {
|
||||
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug
|
||||
: PearlLogLevelInfo;
|
||||
[composer addAttachmentData:[[[PearlLogger get] formatMessagesWithLevel:logLevel] dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@-%@.log",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
|
||||
[PearlKeyChain deviceIdentifier])];
|
||||
mimeType:@"text/plain"
|
||||
fileName:PearlString(@"%@-%@.log",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
|
||||
[PearlKeyChain deviceIdentifier])];
|
||||
}
|
||||
|
||||
[self presentModalViewController:composer animated:YES];
|
||||
}
|
||||
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
|
||||
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -503,15 +787,21 @@
|
||||
#endif
|
||||
{
|
||||
inf(@"Action: Sign out");
|
||||
[[MPAppDelegate get] signOut];
|
||||
[[MPAppDelegate get] signOutAnimated:YES];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
wrn(@"Unsupported action: %u", buttonIndex - [sheet firstOtherButtonIndex]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointAction];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
|
||||
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
|
||||
[[MPiOSConfig get].helpHidden boolValue]? @"Show Help": @"Hide Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out",
|
||||
nil];
|
||||
}
|
||||
|
||||
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result
|
||||
@@ -538,25 +828,25 @@
|
||||
@"You will need to update your account's old password to the new one."
|
||||
do:^{
|
||||
// Update password type.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
|
||||
if ([self.activeElement.algorithm classOfType:type] != self.activeElement.typeClass)
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(
|
||||
type)
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
[[MPAppDelegate managedObjectContextIfReady] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:[self.activeElement.algorithm classNameOfType:type]
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContextIfReady]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.user = self.activeElement.user;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
newElement.version = self.activeElement.version;
|
||||
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
[[MPAppDelegate managedObjectContextIfReady] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
}];
|
||||
|
||||
self.activeElement.type = type;
|
||||
|
||||
if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
|
||||
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated
|
||||
object:self.activeElement];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -577,30 +867,35 @@
|
||||
self.activeElement.name, self.activeElement.name)];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
if ([[MPiOSConfig get].firstRun boolValue])
|
||||
if (![[MPiOSConfig get].typeTipShown boolValue])
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.typeTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.typeTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
[MPiOSConfig get].typeTipShown = PearlBool(YES);
|
||||
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.typeTipContainer.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
|
||||
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated object:self.activeElement];
|
||||
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", self.activeElement.typeShortName)];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(
|
||||
self.activeElement.type), @"type",
|
||||
self.activeElement.typeName,
|
||||
@"type",
|
||||
PearlUnsignedInteger(self.activeElement.version),
|
||||
@"version",
|
||||
nil]];
|
||||
}
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
[self updateAnimated:YES];
|
||||
}
|
||||
|
||||
@@ -608,6 +903,8 @@
|
||||
|
||||
if (textField == self.contentField)
|
||||
[self.contentField resignFirstResponder];
|
||||
if (textField == self.userNameField)
|
||||
[self.userNameField resignFirstResponder];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -628,13 +925,32 @@
|
||||
((MPElementStoredEntity *)self.activeElement).content = self.contentField.text;
|
||||
}];
|
||||
}
|
||||
|
||||
if (textField == self.userNameField) {
|
||||
self.userNameField.enabled = NO;
|
||||
if (![[MPiOSConfig get].userNameTipShown boolValue]) {
|
||||
[self showUserNameTip:@"Tap to copy or hold to edit."];
|
||||
[MPiOSConfig get].userNameTipShown = PearlBool(YES);
|
||||
}
|
||||
|
||||
if ([self.userNameField.text length])
|
||||
self.activeElement.userName = self.userNameField.text;
|
||||
else
|
||||
self.activeElement.userName = nil;
|
||||
|
||||
[[MPAppDelegate get] saveContext];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||
navigationType:(UIWebViewNavigationType)navigationType {
|
||||
|
||||
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
|
||||
inf(@"External link: %@", [request URL]);
|
||||
if ([[[request URL] query] isEqualToString:@"outdated"]) {
|
||||
[self searchOutdatedElements];
|
||||
return NO;
|
||||
}
|
||||
|
||||
[TestFlight passCheckpoint:MPCheckpointExternalLink];
|
||||
|
||||
[[UIApplication sharedApplication] openURL:[request URL]];
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
} recurse:NO];
|
||||
|
||||
self.savePasswordSwitch.on = [MPAppDelegate get].activeUser.saveKey;
|
||||
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
|
||||
self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
else
|
||||
if (cell == self.changeMPCell)
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser];
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser didResetBlock:nil];
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
@@ -139,7 +139,7 @@
|
||||
[MPAppDelegate get].activeUser.defaultType = type;
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
self.defaultTypeLabel.text = NSStringShortFromMPElementType([MPAppDelegate get].activeUser.defaultType);
|
||||
self.defaultTypeLabel.text = [[MPAppDelegate get].key.algorithm shortNameOfType:[MPAppDelegate get].activeUser.defaultType];
|
||||
}
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPElementEntity.h"
|
||||
|
||||
typedef enum {
|
||||
MPSearchScopeAll,
|
||||
MPSearchScopeOutdated,
|
||||
} MPSearchScope;
|
||||
|
||||
@protocol MPSearchResultsDelegate<NSObject>
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element;
|
||||
@@ -17,13 +22,13 @@
|
||||
|
||||
@interface MPSearchDelegate : NSObject<UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
@property (strong, nonatomic) UILabel *tipView;
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, readonly) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
@property (strong, nonatomic) UILabel *tipView;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
|
||||
@property (strong, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#import "MPSearchDelegate.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@interface MPSearchDelegate (Private)
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPSearchDelegate
|
||||
@implementation MPSearchDelegate {
|
||||
|
||||
NSFetchedResultsController *_fetchedResultsController;
|
||||
}
|
||||
@synthesize tipView;
|
||||
@synthesize query;
|
||||
@synthesize dateFormatter;
|
||||
@synthesize fetchedResultsController;
|
||||
@synthesize delegate;
|
||||
@synthesize searchDisplayController;
|
||||
@synthesize searchTipContainer;
|
||||
@@ -35,13 +37,6 @@
|
||||
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
|
||||
self.query = @"";
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[MPAppDelegate managedObjectContext]
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
self.fetchedResultsController.delegate = self;
|
||||
|
||||
self.tipView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 170)];
|
||||
self.tipView.textAlignment = UITextAlignmentCenter;
|
||||
self.tipView.backgroundColor = [UIColor clearColor];
|
||||
@@ -62,6 +57,23 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSFetchedResultsController *)fetchedResultsController {
|
||||
|
||||
if (!_fetchedResultsController) {
|
||||
NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady];
|
||||
if (!moc)
|
||||
return nil;
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
_fetchedResultsController.delegate = self;
|
||||
}
|
||||
|
||||
return _fetchedResultsController;
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
@@ -95,7 +107,12 @@
|
||||
|
||||
- (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:^{
|
||||
self.searchTipContainer.alpha = 0;
|
||||
@@ -110,9 +127,10 @@
|
||||
|
||||
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
dbg(@"Search ended with: %@", controller.searchBar.text);
|
||||
controller.searchBar.prompt = nil;
|
||||
controller.searchBar.searchResultsButtonSelected = NO;
|
||||
controller.searchBar.selectedScopeButtonIndex = MPSearchScopeAll;
|
||||
controller.searchBar.showsScopeBar = NO;
|
||||
}
|
||||
|
||||
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
|
||||
@@ -127,26 +145,59 @@
|
||||
if (!controller.active)
|
||||
return NO;
|
||||
|
||||
assert(self.query);
|
||||
[self fetchData];
|
||||
|
||||
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||
self.query, self.query, NilToNSNull([MPAppDelegate get].activeUser)];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
|
||||
|
||||
if (!controller.active)
|
||||
return NO;
|
||||
|
||||
[self fetchData];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)fetchData {
|
||||
|
||||
assert(self.query);
|
||||
assert([MPAppDelegate get].activeUser);
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", [MPAppDelegate get].activeUser];
|
||||
if (self.query.length)
|
||||
predicate = [NSCompoundPredicate
|
||||
andPredicateWithSubpredicates:[NSArray arrayWithObjects:[NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", self.query],
|
||||
predicate, nil]];
|
||||
|
||||
switch ((MPSearchScope)self.searchDisplayController.searchBar.selectedScopeButtonIndex) {
|
||||
|
||||
case MPSearchScopeAll:
|
||||
break;
|
||||
case MPSearchScopeOutdated:
|
||||
predicate = [NSCompoundPredicate
|
||||
andPredicateWithSubpredicates:[NSArray arrayWithObjects:[NSPredicate predicateWithFormat:@"requiresExplicitMigration_ == YES"],
|
||||
predicate, nil]];
|
||||
break;
|
||||
}
|
||||
self.fetchedResultsController.fetchRequest.predicate = predicate;
|
||||
|
||||
NSError *error;
|
||||
if (![self.fetchedResultsController performFetch:&error])
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
NSArray *subviews = self.searchDisplayController.searchBar.superview.subviews;
|
||||
NSUInteger overlayIndex = [subviews indexOfObject:self.searchDisplayController.searchBar] + 1;
|
||||
UIView *overlay = [subviews count] > overlayIndex? [subviews objectAtIndex:overlayIndex]: nil;
|
||||
if (overlay == self.searchDisplayController.searchResultsTableView || ![overlay isKindOfClass:[UIControl class]])
|
||||
overlay = nil;
|
||||
if (self.tipView.superview != overlay) {
|
||||
[self.tipView removeFromSuperview];
|
||||
[overlay addSubview:self.tipView];
|
||||
}
|
||||
|
||||
return YES;
|
||||
[self.searchDisplayController.searchBar.superview enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
CGRect searchBarFrame = self.searchDisplayController.searchBar.frame;
|
||||
if ([subview isKindOfClass:[UIControl class]] &&
|
||||
CGPointEqualToPoint(
|
||||
CGPointDistanceBetweenCGPoints(searchBarFrame.origin, subview.frame.origin),
|
||||
CGPointMake(0, searchBarFrame.size.height))) {
|
||||
[self.tipView removeFromSuperview];
|
||||
[subview addSubview:self.tipView];
|
||||
*stop = YES;
|
||||
}
|
||||
} recurse:NO];
|
||||
}
|
||||
|
||||
// See MP-14, also crashes easily on internal assertions etc..
|
||||
@@ -204,6 +255,7 @@
|
||||
|
||||
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
|
||||
|
||||
dbg(@"controllerDidChangeContent on thread: %@", [NSThread currentThread].name);
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
// [self.searchDisplayController.searchResultsTableView endUpdates];
|
||||
}
|
||||
@@ -305,19 +357,20 @@
|
||||
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementType type = [MPAppDelegate get].activeUser.defaultType;
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmDefault classNameOfType:type]
|
||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||
assert([MPAppDelegate get].activeUser);
|
||||
|
||||
element.name = siteName;
|
||||
element.user = [MPAppDelegate get].activeUser;
|
||||
element.type = type;
|
||||
element.name = siteName;
|
||||
element.user = [MPAppDelegate get].activeUser;
|
||||
element.type = type;
|
||||
element.version = MPAlgorithmDefaultVersion;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate didSelectElement:element];
|
||||
});
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +406,8 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[TestFlight passCheckpoint:MPCheckpointDeleteElement];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointDeleteElement
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSStringFromMPElementType(element.type), @"type",
|
||||
element.typeName, @"type",
|
||||
PearlUnsignedInteger(element.version), @"version",
|
||||
nil]];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -79,7 +79,12 @@
|
||||
|
||||
assert(self.navigationController.topViewController == self);
|
||||
|
||||
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
|
||||
MPElementType type = [self typeAtIndexPath:indexPath];
|
||||
if (type == NSNotFound)
|
||||
// Selected a non-type row.
|
||||
return;
|
||||
|
||||
[delegate didSelectType:type];
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
@@ -106,8 +111,9 @@
|
||||
case 7:
|
||||
return NSNotFound;
|
||||
|
||||
default:
|
||||
default: {
|
||||
Throw(@"Unsupported row: %d, when selecting generated element type.", indexPath.row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +129,9 @@
|
||||
case 3:
|
||||
return NSNotFound;
|
||||
|
||||
default:
|
||||
default: {
|
||||
Throw(@"Unsupported row: %d, when selecting stored element type.", indexPath.row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,20 @@
|
||||
@interface MPUnlockViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordFieldLabel;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *passwordField;
|
||||
@property (weak, nonatomic) IBOutlet UIView *passwordView;
|
||||
@property (weak, nonatomic) IBOutlet UIScrollView *avatarsView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *oldNameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *avatarTemplate;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
|
||||
@property (weak, nonatomic) IBOutlet UIView *createPasswordTipView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *tip;
|
||||
@property (weak, nonatomic) IBOutlet UIView *passwordTipView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *wordWall;
|
||||
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
|
||||
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *loadingUsersIndicator;
|
||||
|
||||
@property (nonatomic, strong) UIColor *avatarShadowColor;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *selectedUser;
|
||||
@property (strong, nonatomic) NSMutableDictionary *avatarToUser;
|
||||
@property (nonatomic) BOOL wordWallAnimating;
|
||||
@property (nonatomic, strong) NSArray *wordList;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,19 +27,25 @@
|
||||
@synthesize selectedUser;
|
||||
@synthesize avatarToUser;
|
||||
@synthesize spinner;
|
||||
@synthesize passwordFieldLabel;
|
||||
@synthesize passwordField;
|
||||
@synthesize passwordView;
|
||||
@synthesize avatarsView;
|
||||
@synthesize nameLabel, oldNameLabel;
|
||||
@synthesize avatarTemplate;
|
||||
@synthesize deleteTip;
|
||||
@synthesize createPasswordTipView;
|
||||
@synthesize tip;
|
||||
@synthesize passwordTipView;
|
||||
@synthesize passwordTipLabel;
|
||||
@synthesize wordWall;
|
||||
@synthesize targetedUserActionGesture;
|
||||
@synthesize loadingUsersIndicator;
|
||||
@synthesize avatarShadowColor = _avatarShadowColor;
|
||||
@synthesize wordWallAnimating = _wordWallAnimating;
|
||||
@synthesize wordList = _wordList;
|
||||
|
||||
|
||||
- (void)initAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
- (void)initializeAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
|
||||
UIScrollView *alertAvatarScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(12, 30, 260, 150)];
|
||||
alertAvatarScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
|
||||
@@ -66,7 +75,7 @@
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected)
|
||||
user.avatar = (unsigned)avatar.tag;
|
||||
@@ -80,7 +89,7 @@
|
||||
[alertAvatarScrollView setContentOffset:selectedOffset animated:YES];
|
||||
}
|
||||
|
||||
- (void)initConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
- (void)initializeConfirmationAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user {
|
||||
|
||||
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(12, 70, 260, 110)];
|
||||
[alert addSubview:container];
|
||||
@@ -121,6 +130,30 @@
|
||||
self.avatarTemplate.hidden = YES;
|
||||
self.spinner.alpha = 0;
|
||||
self.passwordTipView.alpha = 0;
|
||||
self.createPasswordTipView.alpha = 0;
|
||||
|
||||
NSMutableArray *wordListLines = [NSMutableArray arrayWithCapacity:27413];
|
||||
[[[NSString alloc] initWithData:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"dictionary" withExtension:@"lst"]]
|
||||
encoding:NSUTF8StringEncoding] enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
|
||||
[wordListLines addObject:line];
|
||||
}];
|
||||
self.wordList = wordListLines;
|
||||
|
||||
self.wordWall.alpha = 0;
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
[self initializeWordLabel:wordLabel];
|
||||
} recurse:NO];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:PersistentStoreDidChange object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:PersistentStoreDidMergeChanges object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
|
||||
[self updateLayoutAnimated:NO allowScroll:YES completion:nil];
|
||||
|
||||
@@ -135,10 +168,14 @@
|
||||
[self setAvatarsView:nil];
|
||||
[self setNameLabel:nil];
|
||||
[self setAvatarTemplate:nil];
|
||||
[self setDeleteTip:nil];
|
||||
[self setTip:nil];
|
||||
[self setPasswordTipView:nil];
|
||||
[self setPasswordTipLabel:nil];
|
||||
[self setTargetedUserActionGesture:nil];
|
||||
[self setWordWall:nil];
|
||||
[self setCreatePasswordTipView:nil];
|
||||
[self setPasswordFieldLabel:nil];
|
||||
[self setLoadingUsersIndicator:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
@@ -155,6 +192,11 @@
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
if (!animated)
|
||||
[[self findTargetedAvatar] setSelected:YES];
|
||||
else
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@@ -166,9 +208,19 @@
|
||||
|
||||
- (void)updateUsers {
|
||||
|
||||
NSManagedObjectContext *moc = [MPAppDelegate managedObjectContextIfReady];
|
||||
if (!moc) {
|
||||
self.tip.text = @"Loading...";
|
||||
[self.loadingUsersIndicator startAnimating];
|
||||
return;
|
||||
}
|
||||
|
||||
self.tip.text = @"Tap and hold to delete or reset.";
|
||||
[self.loadingUsersIndicator stopAnimating];
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
|
||||
NSArray *users = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:nil];
|
||||
NSArray *users = [moc executeFetchRequest:fetchRequest error:nil];
|
||||
|
||||
// Clean up avatars.
|
||||
for (UIView *subview in [self.avatarsView subviews])
|
||||
@@ -201,14 +253,14 @@
|
||||
avatar.tag = user.avatar;
|
||||
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString(@"avatar-%u", user.avatar)]
|
||||
forState:UIControlStateNormal];
|
||||
forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:YES];
|
||||
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
|
||||
if (highlighted || selected)
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected) {
|
||||
if ((self.selectedUser = user))
|
||||
@@ -219,7 +271,7 @@
|
||||
self.selectedUser = nil;
|
||||
[self didToggleUserSelection];
|
||||
}
|
||||
} options:0];
|
||||
} options:0];
|
||||
|
||||
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
|
||||
|
||||
@@ -240,21 +292,25 @@
|
||||
}
|
||||
|
||||
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
if (self.selectedUser)
|
||||
[self.passwordField becomeFirstResponder];
|
||||
if (self.selectedUser)
|
||||
[self.passwordField becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
|
||||
|
||||
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
__block MPUserEntity *newUser = nil;
|
||||
[[MPAppDelegate managedObjectContextIfReady] performBlockAndWait:^{
|
||||
newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContextIfReady]];
|
||||
}];
|
||||
|
||||
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished){
|
||||
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished) {
|
||||
newUserAvatar.selected = NO;
|
||||
if (!finished)
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:newUser];
|
||||
[[MPAppDelegate managedObjectContextIfReady] performBlock:^{
|
||||
[[MPAppDelegate managedObjectContextIfReady] deleteObject:newUser];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -278,7 +334,7 @@
|
||||
newUser.name = [alert textFieldAtIndex:0].text;
|
||||
[self showNewUserAvatarAlertFor:newUser completion:completion];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonSave, nil];
|
||||
}
|
||||
|
||||
- (void)showNewUserAvatarAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
@@ -286,13 +342,13 @@
|
||||
[PearlAlert showAlertWithTitle:@"Choose Your Avatar"
|
||||
message:@"\n\n\n\n\n\n" viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:^(UIAlertView *_alert, UITextField *_firstField) {
|
||||
[self initAvatarAlert:_alert forUser:newUser];
|
||||
[self initializeAvatarAlert:_alert forUser:newUser];
|
||||
}
|
||||
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
|
||||
tappedButtonBlock:^(UIAlertView *_alert, NSInteger _buttonIndex) {
|
||||
|
||||
// Okay
|
||||
[self showNewUserConfirmationAlertFor:newUser completion:completion];
|
||||
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
// Okay
|
||||
[self showNewUserConfirmationAlertFor:newUser completion:completion];
|
||||
} cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
}
|
||||
|
||||
- (void)showNewUserConfirmationAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
|
||||
@@ -303,21 +359,21 @@
|
||||
@"\n\n\n\n\n\n"
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:^void(UIAlertView *__alert, UITextField *__firstField) {
|
||||
[self initConfirmationAlert:__alert forUser:newUser];
|
||||
[self initializeConfirmationAlert:__alert forUser:newUser];
|
||||
}
|
||||
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
|
||||
if (__buttonIndex == [__alert cancelButtonIndex]) {
|
||||
[self showNewUserNameAlertFor:newUser completion:completion];
|
||||
return;
|
||||
}
|
||||
tappedButtonBlock:^void(UIAlertView *__alert, NSInteger __buttonIndex) {
|
||||
if (__buttonIndex == [__alert cancelButtonIndex]) {
|
||||
[self showNewUserNameAlertFor:newUser completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm
|
||||
completion(YES);
|
||||
self.selectedUser = newUser;
|
||||
// Confirm
|
||||
completion(YES);
|
||||
self.selectedUser = newUser;
|
||||
|
||||
[self updateUsers];
|
||||
}
|
||||
cancelTitle:@"Change" otherTitles:@"Confirm", nil];
|
||||
[self updateUsers];
|
||||
}
|
||||
cancelTitle:@"Change" otherTitles:@"Confirm", nil];
|
||||
}
|
||||
|
||||
- (void)updateLayoutAnimated:(BOOL)animated allowScroll:(BOOL)allowScroll completion:(void (^)(BOOL finished))completion {
|
||||
@@ -332,14 +388,16 @@
|
||||
|
||||
self.oldNameLabel.alpha = 0;
|
||||
self.nameLabel.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
} completion:^(BOOL finished) {
|
||||
if (completion)
|
||||
completion(finished);
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
// Lay out password entry and user selection views.
|
||||
if (self.selectedUser && !self.passwordView.alpha) {
|
||||
// User was just selected.
|
||||
self.passwordView.alpha = 1;
|
||||
self.avatarsView.center = CGPointMake(160, 170);
|
||||
self.avatarsView.scrollEnabled = NO;
|
||||
@@ -347,9 +405,9 @@
|
||||
self.nameLabel.backgroundColor = [UIColor blackColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor whiteColor];
|
||||
self.deleteTip.alpha = 0;
|
||||
} else
|
||||
if (!self.selectedUser && self.passwordView.alpha == 1) {
|
||||
// User was just deselected.
|
||||
self.passwordField.text = nil;
|
||||
self.passwordView.alpha = 0;
|
||||
self.avatarsView.center = CGPointMake(160, 310);
|
||||
@@ -358,9 +416,36 @@
|
||||
self.nameLabel.backgroundColor = [UIColor clearColor];
|
||||
self.oldNameLabel.center = self.nameLabel.center;
|
||||
self.avatarShadowColor = [UIColor lightGrayColor];
|
||||
self.deleteTip.alpha = 0.5;
|
||||
}
|
||||
|
||||
// Lay out the word wall.
|
||||
if (!self.selectedUser || self.selectedUser.keyID) {
|
||||
self.passwordFieldLabel.text = @"Enter your master password:";
|
||||
|
||||
self.wordWall.alpha = 0;
|
||||
self.createPasswordTipView.alpha = 0;
|
||||
self.wordWallAnimating = NO;
|
||||
} else {
|
||||
self.passwordFieldLabel.text = @"Create your master password:";
|
||||
|
||||
if (!self.wordWallAnimating) {
|
||||
self.wordWallAnimating = YES;
|
||||
self.wordWall.alpha = 1;
|
||||
self.createPasswordTipView.alpha = 1;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Jump out of our UIView animation block.
|
||||
[self beginWordWallAnimation];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.createPasswordTipView.alpha = 0;
|
||||
}];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Lay out user targeting.
|
||||
MPUserEntity *targetedUser = self.selectedUser;
|
||||
UIButton *selectedAvatar = [self avatarForUser:self.selectedUser];
|
||||
UIButton *targetedAvatar = selectedAvatar;
|
||||
@@ -381,7 +466,7 @@
|
||||
avatar.alpha = isTargeted? 1: self.selectedUser? 0.1: 0.4;
|
||||
|
||||
[self updateAvatarShadowColor:avatar isTargeted:isTargeted];
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
|
||||
if (allowScroll) {
|
||||
CGPoint targetContentOffset = CGPointMake(MAX(0, targetedAvatar.center.x - self.avatarsView.bounds.size.width / 2),
|
||||
@@ -390,7 +475,8 @@
|
||||
[self.avatarsView setContentOffset:targetContentOffset animated:animated];
|
||||
}
|
||||
|
||||
self.nameLabel.text = targetedUser? targetedUser.name: @"New User";
|
||||
// Lay out user name label.
|
||||
self.nameLabel.text = targetedAvatar? targetedUser? targetedUser.name: @"New User": nil;
|
||||
self.nameLabel.bounds = CGRectSetHeight(self.nameLabel.bounds,
|
||||
[self.nameLabel.text sizeWithFont:self.nameLabel.font
|
||||
constrainedToSize:CGSizeMake(self.nameLabel.bounds.size.width - 10, 100)
|
||||
@@ -400,6 +486,36 @@
|
||||
completion(YES);
|
||||
}
|
||||
|
||||
- (void)beginWordWallAnimation {
|
||||
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
if (wordLabel.frame.origin.x < -self.wordWall.frame.size.width / 3) {
|
||||
wordLabel.frame = CGRectSetX(wordLabel.frame, wordLabel.frame.origin.x + self.wordWall.frame.size.width);
|
||||
[self initializeWordLabel:wordLabel];
|
||||
}
|
||||
} recurse:NO];
|
||||
|
||||
if (self.wordWallAnimating)
|
||||
[UIView animateWithDuration:15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
wordLabel.frame = CGRectSetX(wordLabel.frame, wordLabel.frame.origin.x - self.wordWall.frame.size.width / 3);
|
||||
} recurse:NO];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[self beginWordWallAnimation];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)initializeWordLabel:(UILabel *)wordLabel {
|
||||
|
||||
wordLabel.alpha = 0.05 + (random() % 35) / 100.0F;
|
||||
wordLabel.text = [self.wordList objectAtIndex:(NSUInteger)random() % [self.wordList count]];
|
||||
}
|
||||
|
||||
- (void)setPasswordTip:(NSString *)string {
|
||||
|
||||
if (string.length)
|
||||
@@ -420,7 +536,7 @@
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (unlocked)
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
|
||||
else {
|
||||
if (self.passwordField.text.length)
|
||||
[self setPasswordTip:@"Incorrect password."];
|
||||
@@ -509,7 +625,8 @@
|
||||
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
|
||||
|
||||
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
|
||||
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation, nil];
|
||||
group.animations = [NSArray arrayWithObjects:toShadowColorAnimation, toShadowOpacityAnimation, pulseShadowOpacityAnimation,
|
||||
nil];
|
||||
group.duration = MAXFLOAT;
|
||||
|
||||
[avatar.layer removeAnimationForKey:@"inactiveShadow"];
|
||||
@@ -574,8 +691,8 @@
|
||||
|
||||
[self tryMasterPassword];
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
|
||||
|
||||
return YES;
|
||||
@@ -617,20 +734,27 @@
|
||||
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
|
||||
if (!targetedUser)
|
||||
return;
|
||||
|
||||
[PearlSheet showSheetWithTitle:targetedUser.name
|
||||
message:nil
|
||||
viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
[self updateUsers];
|
||||
} else if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
[PearlSheet showSheetWithTitle:targetedUser.name
|
||||
message:nil viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
[[MPAppDelegate get].managedObjectContextIfReady performBlockAndWait:^{
|
||||
[[MPAppDelegate get].managedObjectContextIfReady deleteObject:targetedUser];
|
||||
}];
|
||||
[[MPAppDelegate get] saveContext];
|
||||
[self updateUsers];
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser didResetBlock:^{
|
||||
[[self avatarForUser:targetedUser] setSelected:YES];
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
|
||||
@property (nonatomic, retain) NSNumber *sendInfo;
|
||||
@property (nonatomic, retain) NSNumber *helpHidden;
|
||||
@property (nonatomic, retain) NSNumber *userNameHidden;
|
||||
@property (nonatomic, retain) NSNumber *showQuickStart;
|
||||
@property (nonatomic, retain) NSNumber *actionsTipShown;
|
||||
@property (nonatomic, retain) NSNumber *typeTipShown;
|
||||
@property (nonatomic, retain) NSNumber *userNameTipShown;
|
||||
|
||||
+ (MPiOSConfig *)get;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
@implementation MPiOSConfig
|
||||
@dynamic sendInfo, helpHidden, showQuickStart;
|
||||
@dynamic sendInfo, helpHidden, userNameHidden, showQuickStart, actionsTipShown, typeTipShown, userNameTipShown;
|
||||
|
||||
- (id)init {
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendInfo)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(userNameHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
|
||||
@"510296984", NSStringFromSelector(@selector(iTunesID)),
|
||||
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(actionsTipShown)),
|
||||
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(typeTipShown)),
|
||||
PearlBool(NO), NSStringFromSelector(@selector(userNameTipShown)),
|
||||
nil]];
|
||||
|
||||
return self;
|
||||
|
||||
BIN
Resources/AppStore-iOS.png
Normal file
|
After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
Resources/Tooltips/tip_basic_black_bottom_right.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Resources/Tooltips/tip_basic_black_bottom_right@2x.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
27413
Resources/dictionary.lst
Normal file
@@ -55,7 +55,7 @@
|
||||
$(".Class").css("display", "none");
|
||||
if (!$(".Class." + activeClass).length)
|
||||
return "Not found: " + activeClass;
|
||||
|
||||
|
||||
$(".Class." + activeClass).css("display", "block");
|
||||
}
|
||||
</script>
|
||||
@@ -75,13 +75,13 @@
|
||||
<b>While searching</b>, the names of previously used sites will be listed.<br />
|
||||
Tap one of these results to go straight to its password.
|
||||
</p>
|
||||
|
||||
|
||||
<h2 id="2">— 2 —</h2>
|
||||
<p>
|
||||
<b>The site</b>'s password is now displayed.<br />
|
||||
Tap it to <i>copy the password</i>. Once copied, you can switch to another application and paste it into a password field.
|
||||
</p>
|
||||
|
||||
|
||||
<p class="Class MPElementStoredEntity">
|
||||
<b>To change</b> the password for this site, tap the <i>edit icon</i> <img src="icon_edit.png" />.
|
||||
</p>
|
||||
@@ -90,7 +90,7 @@
|
||||
<b>Below the password</b> you can set the <i>password type</i>. Some types <i>create a password for you</i>,
|
||||
others let you <i>choose your own</i>.
|
||||
</p>
|
||||
|
||||
|
||||
<p class="Class MPElementGeneratedEntity">
|
||||
<b>If the site complains</b> when you try to set or update the password, try changing the password type.
|
||||
</p>
|
||||
@@ -173,7 +173,7 @@
|
||||
share the password with anyone else. Instead, the app creates secure passwords for use with whatever site
|
||||
or purpose you might need a password for.
|
||||
</p>
|
||||
|
||||
|
||||
<h3 id="custom">I can't change all my passwords.<br />
|
||||
Some of them were assigned to me.</h3>
|
||||
<p>
|
||||
@@ -295,6 +295,34 @@
|
||||
I invite anyone with a technical background to go through these resources to make certain of the trustworthiness of Master Password.
|
||||
</p>
|
||||
|
||||
<h3 id="outdated">Is the algorithm stable?<br />
|
||||
Will my passwords ever change?</h3>
|
||||
<p>
|
||||
While we're very confident of the strength of the Master Password algorithm, we're also constantly keeping an eye out
|
||||
for what the evolutions are of hackers' tools and capabilities. To give you the best possible protection, there is
|
||||
always the possibility that we'll have to make tweaks to the Master Password algorithm in order to fend off any
|
||||
attempts at breaking in.
|
||||
</p>
|
||||
<p>
|
||||
Usually, these tweaks will be automatically applied when you install the latest version. In this case, you will notice
|
||||
nothing and all you need to take away from this is that it's best to always be running the latest version of Master Password.
|
||||
</p>
|
||||
<p>
|
||||
It is possible, however, that to apply an upgrade to your passwords, a new password will need to be set for your site's
|
||||
account. In this case, Master Password will leave your passwords the way they are but give you the <em>option</em> of
|
||||
upgrading your passwords when it's convenient to you. Whenever you're ready, just tap the upgrade password icon and
|
||||
Master Password will show you the old password and the new one so that you can easily update your site's account.
|
||||
</p>
|
||||
<p>
|
||||
<em>Please note</em>: if Master Password warns you that you have outdated passwords, it's best to upgrade them all
|
||||
as soon as convenient. If you lose your device or data and recreate your Master Password user on another device,
|
||||
Master Password can only regenerate the passwords for you that you've upgraded. iCloud/iTunes sync or exports are not
|
||||
affected, so these are good ways to safely back up your passwords.
|
||||
</p>
|
||||
<p>
|
||||
<a href="?outdated">Tap here</a> to check if you have any outdated passwords.
|
||||
</p>
|
||||
|
||||
<h3 id="branded">This stuff is gold.<br />
|
||||
I want one branded for our company.</h3>
|
||||
<p>
|
||||
@@ -304,7 +332,7 @@
|
||||
<p>
|
||||
Master Password can also be used as a One-Time Password token generator to secure your infrastructure and client access.
|
||||
</p>
|
||||
|
||||
|
||||
<footer>
|
||||
<a href="http://masterpassword.lyndir.com">Homepage</a> | <a href="http://www.lyndir.com">Lyndir</a> |
|
||||
<a href="http://www.lyndir.com/contact">Contact</a>
|
||||
|
||||
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 545 KiB |
BIN
Resources/iTunesArtwork-Square.png
Normal file
|
After Width: | Height: | Size: 563 KiB |
1
Site/MasterPassword_PressKit.zip
Symbolic link
@@ -0,0 +1 @@
|
||||
../Press/./MasterPassword_PressKit.zip
|
||||
@@ -83,24 +83,115 @@
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(_ue, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Page JS -->
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(window).scroll(function() {
|
||||
if ($(window).scrollTop() > 100) {
|
||||
$(".appstore").show();
|
||||
$("header .appstore").hide();
|
||||
} else {
|
||||
$(".appstore").hide();
|
||||
$("header .appstore").show();
|
||||
}
|
||||
});
|
||||
$(".tip.appstore").hide().delay(1000).fadeIn();
|
||||
$(".tip.phone").hide();
|
||||
$("#sendtophone").mouseenter(function() {
|
||||
$(".tip.phone").fadeIn();
|
||||
});
|
||||
$("#sendtophone").mouseleave(function() {
|
||||
$(".tip.phone").fadeOut();
|
||||
});
|
||||
$("#sendtophone").submit(function() {
|
||||
$.ajax({
|
||||
url: "http://masterpassword.lyndir.com/send.php",
|
||||
data: {
|
||||
destination: $(this).find("input[type='text']").val(),
|
||||
},
|
||||
cache: false,
|
||||
});
|
||||
|
||||
goog_report_conversion('index-sendtophone');
|
||||
_gaq.push(['_trackPageview', '/outbound/sendtophone']);
|
||||
|
||||
$("#sendtophone .field").hide();
|
||||
$("#sendtophone .confirm").show();
|
||||
return false;
|
||||
});
|
||||
$("#sendtophone .field").show();
|
||||
$("#sendtophone .confirm").hide();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- AdWords -->
|
||||
<script type="text/javascript">
|
||||
/* <![CDATA[ */
|
||||
goog_snippet_vars = function() {
|
||||
var w = window;
|
||||
w.google_conversion_id = 1015576061;
|
||||
w.google_conversion_label = "PcXqCPPz5AIQ_euh5AM";
|
||||
w.google_conversion_value = 4;
|
||||
}
|
||||
goog_report_conversion = function(url) {
|
||||
goog_snippet_vars();
|
||||
window.google_conversion_format = "3";
|
||||
window.google_is_call = true;
|
||||
var opt = new Object();
|
||||
opt.onload_callback = function() {
|
||||
if (typeof(url) != 'undefined') {
|
||||
window.location = url;
|
||||
}
|
||||
}
|
||||
var conv_handler = window['google_trackConversion'];
|
||||
if (typeof(conv_handler) == 'function') {
|
||||
conv_handler(opt);
|
||||
}
|
||||
}
|
||||
/* ]]> */
|
||||
</script>
|
||||
<script type="text/javascript" src="http://www.googleadservices.com/pagead/conversion_async.js"></script>
|
||||
|
||||
<!-- Google +1 -->
|
||||
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
||||
<a class="badge appstore" href="http://itunes.apple.com/app/id510296984" onclick="goog_report_conversion('index-fixed-header');_gaq.push(['_trackPageview', '/outbound/itunes']);"><img src="img/appstore.png" /></a>
|
||||
<header>
|
||||
|
||||
<a class="appstore" href="http://itunes.com/apps/MasterPassword"><img src="img/appstore.png" /></a>
|
||||
<h1><a href="index.html"><img class="logo" src="img/iTunesArtwork-Bare.png" /> Master Password</a></h1>
|
||||
<div class="badge appstore">
|
||||
<a href="http://itunes.apple.com/app/id510296984" onclick="goog_report_conversion('index-top-header');_gaq.push(['_trackPageview', '/outbound/itunes']);">
|
||||
<!--span class="tip appstore">Great feedback may earn you a free copy for a friend!</span-->
|
||||
<img src="img/appstore.png" />
|
||||
</a><br />
|
||||
<form id="sendtophone">
|
||||
<span class="field">
|
||||
Or send to your phone:<br />
|
||||
<input type="text" name="email" placeholder="E-mail or phone number" />
|
||||
<span class="tip phone">Phone needs country code (eg. +1 for US/CA, +44 for UK)</span>
|
||||
</span>
|
||||
<span class="confirm">
|
||||
Message sent!
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<h1><a href="."><img class="logo" src="img/iTunesArtwork-Bare.png" /> Master Password</a></h1>
|
||||
<div class="footnote"><a href="mailto:masterpassword+remove_this@lyndir.com" onclick="_gaq.push(['_trackPageview', '/outbound/mail']);">Contact</a> | <a href="http://www.lyndir.com" onclick="_gaq.push(['_trackPageview', '/outbound/lyndir.com']);">Lyndir</a> | <a href="https://plus.google.com/116256327773442623984/about" rel="publisher" onclick="_gaq.push(['_trackPageview', '/outbound/google+']);">Google+</a></div>
|
||||
<div class="divider"></div>
|
||||
|
||||
</header>
|
||||
<div id="fixedheader">
|
||||
<h2><a href="index.html">Master Password</a></h2>
|
||||
<h2><a href=".">Master Password</a></h2>
|
||||
</div>
|
||||
<!--a href="http://bit.ly/vNN5Zi" onclick="_gaq.push(['_trackPageview', '/outbound/testflight']);" id="ribbon"></a-->
|
||||
|
||||
<section class="heading">
|
||||
<div>
|
||||
<h1>So how does it work?</h1>
|
||||
<h1>So how does it work? <div class="g-plusone" data-annotation="none" data-href="http://masterpassword.lyndir.com/algorithm.html"></div></h1>
|
||||
|
||||
<p>
|
||||
The theory behind Master Password is simple. The user remembers a single, secure password. The user only ever uses that password to log into the Master Password application. This master password is then used as a seed to generate a different password based on the name of the site to generate a password for.
|
||||
@@ -118,13 +209,13 @@
|
||||
<p>
|
||||
Master Password uses a stateless algorithm that relies solely on its implementation and the user's inputs. The user is expected to remember the following information:
|
||||
<ul>
|
||||
<li><b>The master password</b> (eg. <em>pink fluffy door frame</em>):<br />
|
||||
<li><strong>The master password</strong> (eg. <em>pink fluffy door frame</em>):<br />
|
||||
This is a secret that the user shares with nobody.</li>
|
||||
<li><b>The site name</b> (eg. <em>apple.com</em>):<br />
|
||||
<li><strong>The site name</strong> (eg. <em>apple.com</em>):<br />
|
||||
The user chooses a name for each site. Its domain name is an ideal choice, since it needn't necessarily be remembered.</li>
|
||||
<li><b>The site's password counter</b> (default: <em>0</em>):<br />
|
||||
<li><strong>The site's password counter</strong> (default: <em>0</em>):<br />
|
||||
This is an integer that can be incremented when the user needs a new password for the site.</li>
|
||||
<li><b>The site's password type</b> (default: <em>Long Password</em>):<br />
|
||||
<li><strong>The site's password type</strong> (default: <em>Long Password</em>):<br />
|
||||
This type determines the format of the output password. It can be changed if the site's password policy does not accept passwords of this format.</li>
|
||||
</ul>
|
||||
</p>
|
||||
@@ -132,26 +223,33 @@
|
||||
In short, the algorithm is comprised of the following steps:
|
||||
<ul>
|
||||
<li>Determining the master <code>key</code></li>
|
||||
<li>Determining the cipher <code>seed</code></li>
|
||||
<li>Determining the template <code>seed</code></li>
|
||||
<li>Encoding a user-friendly <code>password</code></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
A note on types:
|
||||
<ul>
|
||||
<li>Any character string is UTF-8 de- or encoded, depending on context.</li>
|
||||
<li>Any number is converted to 32-bit network byte order.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h2>The Master Password</h2>
|
||||
<p>
|
||||
The user chooses a single master password, preferably sufficiently long to harden against brute-force attacks. Master Password recommends absurd two or three-word sentences as they're easily remembered and generally sufficiently high in entropy.
|
||||
The user chooses a single master password, preferably sufficiently long to harden against brute-force attacks. Master Password recommends absurd three or four-word sentences as they're easily remembered and generally sufficiently high in entropy.
|
||||
</p>
|
||||
<p>
|
||||
The application then creates a <a href="http://www.tarsnap.com/scrypt.html" onclick="_gaq.push(['_trackPageview', '/outbound/tarsnap.com/scrypt.html">scrypt</a> key derivative from the user's password. This process takes quite a bit of processing time and memory. This step exists to make brute-force attempts at guessing the master password from a given output password <b>far more difficult</b>, to practically infeasible, even for otherwise vulnerable password strings.
|
||||
The application then creates a <a href="http://www.tarsnap.com/scrypt.html" onclick="_gaq.push(['_trackPageview', '/outbound/tarsnap.com/scrypt.html">scrypt</a> key derivative from the user's password. This process takes quite a bit of processing time and memory. This step exists to make brute-force attempts at guessing the master password from a given output password <strong>far more difficult</strong>, to practically infeasible, even for otherwise vulnerable password strings.
|
||||
</p>
|
||||
<code><pre>
|
||||
key = scrypt( P, S, N, r, p, dkLen )
|
||||
where
|
||||
P = master password (UTF-8)
|
||||
S = <empty>
|
||||
N = 16384
|
||||
P = master password
|
||||
S = "com.lyndir.masterpassword" . name length . name
|
||||
N = 32768
|
||||
r = 8
|
||||
p = 1
|
||||
p = 2
|
||||
dkLen = 64
|
||||
</pre></code>
|
||||
|
||||
@@ -161,137 +259,147 @@
|
||||
|
||||
<h2>Combining The Inputs</h2>
|
||||
<p>
|
||||
The theory behind Master Password requires that all inputs are given by the user. The two main inputs are the master password that we used to determine the <code>key</code> and the site's name. There is a third input value, the password counter, which is a 32-bit unsigned integer value that is used to salt the input. Initially, the password counter should be zero, but a user may specify a non-zero counter value in case he wants to force the algorithm to produce a new output password for the site.
|
||||
The theory behind Master Password requires that all inputs are given by the user. The two main inputs are the master password that we used to determine the <code>key</code> and the site's name. There is a third input value, the password counter, which is a 32-bit unsigned integer value. Initially, the password counter should be zero, but a user may specify a non-zero counter value in case he wants to force the algorithm to produce a new output password for the site.
|
||||
</p>
|
||||
<p>
|
||||
These input values are combined in a byte array, separated by a single <code>NUL</code> byte. In order, the input values are the <code>site name</code> (UTF-8 decoded), the master <code>key</code>, and a <code>salt</code> (this is the password counter, a 32-bit unsigned integer in network byte order). The byte array is hashed using the SHA-1 algorithm to yield the <code>seed</code> as a result.
|
||||
These input values are combined in a byte array, separated by a single <code>NUL</code> byte. In order, the input values are the <code>site name</code>, the master <code>key</code>, and a <code>counter</code>. The byte array is hashed using the HMAC-SHA-256 algorithm to yield the <code>seed</code> as a result.
|
||||
</p>
|
||||
<code><pre>
|
||||
salt = htonl( password counter )
|
||||
seed = sha1( site name . "\0" . key . "\0" . salt )
|
||||
seed = hmac-sha256( key, "com.lyndir.masterpassword" . site name length . site name . counter )
|
||||
</pre></code>
|
||||
|
||||
<h2>Generating The Output</h2>
|
||||
<p>
|
||||
We now have a <code>seed</code> which is a sufficiently long seemingly-arbitrary string of bytes that is unique to the site and the user. This string of bytes, however, is not very useful for a user to use as a password. We have two additional problems that need to be solved: The output password must be easy for a user to read and copy, but it should also be compatible with most password policies.
|
||||
We now have a <code>seed</code> which is a sufficiently long seemingly-arbitrary string of bytes that is unique to the site and the user. This string of bytes, however, is not very useful for a user to use as a password. We have two additional problems that need to be solved: The output password must be easy for a user to read and type in using a keyboard or smartphone, but it should also be compatible with most site's password policies.
|
||||
</p>
|
||||
<p>
|
||||
Password policies are strict rules imposed by applications on their users, designed to limit the types of passwords these users are allowed to use with the application. Usually, these policies exist to force users into thinking about passwords with a healthy entropy. Often, they exist purely as a side-effect of bad password handling such as storing the clear-text passwords in a database.
|
||||
</p>
|
||||
<p>
|
||||
Since the idea is that the output password can be used directly as a password to protect the user's account on the site, it needs to be able to pass the site's password policy.
|
||||
Master Password addresses this problem by introducing <em>password types</em>. Each password type describes what an output password must look like and maps to a set of <code>ciphers</code>. Ciphers describe the resulting output password using a series of characters that map to character groups of candidate output characters. A cipher has the same length as the output password it yields. Each character in the cipher maps to a specific character group. At each position of the output password, a character is chosen from the character group identified by the character in the cipher at the same position.
|
||||
Master Password addresses this problem by introducing <em>password types</em>. Each password type describes what an output password must look like and maps to a set of <code>templates</code>. Templates describe the resulting output password using a series of characters that map to character groups of candidate output characters. A template has the same length as the output password it yields. Each character in the template maps to a specific character group. At each position of the output password, a character is chosen from the character group identified by the character in the template at the same position.
|
||||
</p>
|
||||
<p>
|
||||
The following ciphers are defined:
|
||||
The following templates are defined:
|
||||
<ul>
|
||||
<li>Type: <b>Long Password</b></li>
|
||||
<li>
|
||||
<li><p>
|
||||
Type: <strong>Maximum Security Password</strong>
|
||||
<ul>
|
||||
<li><code>CvcvCvcvnoCvcv</code></li>
|
||||
<li><code>CvcvnoCvcvCvcv</code></li>
|
||||
<li><code>CvcvCvcvCvcvno</code></li>
|
||||
<li><code>anoxxxxxxxxxxxxxxxxx</li></code>
|
||||
<li><code>axxxxxxxxxxxxxxxxxno</li></code>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Type: <b>Medium Password</b></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Type: <strong>Long Password</strong>
|
||||
<ul>
|
||||
<li><code>CvcvnoCvcvCvcv</li></code>
|
||||
<li><code>CvcvCvcvnoCvcv</li></code>
|
||||
<li><code>CvcvCvcvCvcvno</li></code>
|
||||
<li><code>CvccnoCvcvCvcv</li></code>
|
||||
<li><code>CvccCvcvnoCvcv</li></code>
|
||||
<li><code>CvccCvcvCvcvno</li></code>
|
||||
<li><code>CvcvnoCvccCvcv</li></code>
|
||||
<li><code>CvcvCvccnoCvcv</li></code>
|
||||
<li><code>CvcvCvccCvcvno</li></code>
|
||||
<li><code>CvcvnoCvcvCvcc</li></code>
|
||||
<li><code>CvcvCvcvnoCvcc</li></code>
|
||||
<li><code>CvcvCvcvCvccno</li></code>
|
||||
<li><code>CvccnoCvccCvcv</li></code>
|
||||
<li><code>CvccCvccnoCvcv</li></code>
|
||||
<li><code>CvccCvccCvcvno</li></code>
|
||||
<li><code>CvcvnoCvccCvcc</li></code>
|
||||
<li><code>CvcvCvccnoCvcc</li></code>
|
||||
<li><code>CvcvCvccCvccno</li></code>
|
||||
<li><code>CvccnoCvcvCvcc</li></code>
|
||||
<li><code>CvccCvcvnoCvcc</li></code>
|
||||
<li><code>CvccCvcvCvccno</li></code>
|
||||
</ul>
|
||||
</p></li>
|
||||
<li><p>Type: <strong>Medium Password</strong>
|
||||
<ul>
|
||||
<li><code>CvcnoCvc</code></li>
|
||||
<li><code>CvcCvcno</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Type: <b>Short Password</b></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Type: <strong>Short Password</strong>
|
||||
<ul>
|
||||
<li><code>Cvcn</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Type: <b>Basic Password</b></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Type: <strong>Basic Password</strong>
|
||||
<ul>
|
||||
<li><code>aaanaaan</code></li>
|
||||
<li><code>aannaaan</code></li>
|
||||
<li><code>aaannaaa</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Type: <b>PIN</b></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Type: <strong>PIN</strong>
|
||||
<ul>
|
||||
<li><code>nnnn</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
</p></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
By default, Master Password uses the <em>Long Password</em> type for any new passwords. The user is able to choose a different password type, which is normally only done if the site's password policy is incompatible with the output password produced by this type.
|
||||
</p>
|
||||
<p>
|
||||
To create the create the output password, the bytes in the <code>seed</code> are encoded according to the cipher. The first <code>seed</code> byte is used to determine which of the type's ciphers to use for encoding an output password. We take the byte value of the first <code>seed</code> byte modulo the amount of ciphers set for the chosen password type and use the result as a zero-based index in the cipher list for the password type.
|
||||
To create the create the output password, the bytes in the <code>seed</code> are encoded according to the template. The first <code>seed</code> byte is used to determine which of the type's templates to use for encoding an output password. We take the byte value of the first <code>seed</code> byte modulo the amount of templates set for the chosen password type and use the result as a zero-based index in the template list for the password type.
|
||||
</p>
|
||||
<code><pre>
|
||||
ciphers = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno" ]
|
||||
cipher = ciphers[ seed[0] % count( ciphers ) ]
|
||||
templates = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno", ... ]
|
||||
template = templates[ seed[0] % count( templates ) ]
|
||||
</pre></code>
|
||||
<p>
|
||||
Now that we know what cipher to use for building our output password, all that's left is to iterate the cipher, and produce a character of password output for each step. When we iterate the cipher (index <code>i</code>), we look in the character group identified by the character (string <code>passChars</code>) in the cipher at index <code>i</code>.
|
||||
Now that we know what template to use for building our output password, all that's left is to iterate the template, and produce a character of password output for each step. When we iterate the template (index <code>i</code>), we look in the character group identified by the character (string <code>passChars</code>) in the template at index <code>i</code>.
|
||||
</p>
|
||||
<p>
|
||||
The following character groups (<code>passChars</code>) are defined:
|
||||
<ul>
|
||||
<li>Cipher character: <code>V</code></li>
|
||||
<li>
|
||||
<li><p>Template character: <code>V</code>
|
||||
<ul>
|
||||
<li><code>AEIOU</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>C</code></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>C</code>
|
||||
<ul>
|
||||
<li><code>BCDFGHJKLMNPQRSTVWXYZ</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>v</code></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>v</code>
|
||||
<ul>
|
||||
<li><code>aeiou</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>c</code></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>c</code>
|
||||
<ul>
|
||||
<li><code>bcdfghjklmnpqrstvwxyz</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>A</code> (<code>= V . C</code>)</li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>A</code> (<code>= V . C</code>)
|
||||
<ul>
|
||||
<li><code>AEIOUBCDFGHJKLMNPQRSTVWXYZ</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>a</code> (<code>= V . v . C . c</code>)</li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>a</code> (<code>= V . v . C . c</code>)
|
||||
<ul>
|
||||
<li><code>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>n</code></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>n</code>
|
||||
<ul>
|
||||
<li><code>0123456789</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>o</code></li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>o</code>
|
||||
<ul>
|
||||
<li><code>!@#$%^&*()</code></li>
|
||||
<li><code>@&%?,=[]_:-+*$#!'^~;()/.</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Cipher character: <code>X</code> (<code>= a . n . o</code>)</li>
|
||||
<li>
|
||||
</p></li>
|
||||
<li><p>Template character: <code>X</code> (<code>= a . n . o</code>)
|
||||
<ul>
|
||||
<li><code>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()</code></li>
|
||||
<li><code>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789@&%?,=[]_:-+*$#!'^~;()/.</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
</p></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
@@ -306,8 +414,30 @@
|
||||
<a class="next" href="https://github.com/Lyndir/MasterPassword">Show me the code!</a>
|
||||
|
||||
</section>
|
||||
|
||||
<span itemscope itemtype="http://schema.org/MobileSoftwareApplication">
|
||||
<meta itemprop="image" content="http://masterpassword.lyndir.com/img/iTunesArtwork-Rounded.png" />
|
||||
<meta itemprop="name" content="Master Password" />
|
||||
<meta itemprop="operatingsystems" content="iOS" />
|
||||
<meta itemprop="softwareversion" content="5.0" />
|
||||
|
||||
<span itemprop="offers" itemscope itemtype="http://schema.org/Offer">
|
||||
<meta itemprop="name" content="iOS" />
|
||||
<meta itemprop="price" content="5.99" />
|
||||
<meta itemprop="priceCurrency" content="USD" />
|
||||
<span itemprop="seller" itemscope itemtype="http://schema.org/Organization">
|
||||
<meta itemprop="name" content="Apple" />
|
||||
<meta itemprop="url" content="http://itunes.apple.com/app/id510296984" />
|
||||
</span>
|
||||
</span>
|
||||
<span itemprop="author" itemscope itemtype="http://schema.org/Organization">
|
||||
<meta itemprop="name" content="Lyndir" />
|
||||
<meta itemprop="url" content="http://www.lyndir.com" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<footer>
|
||||
Master Password is a security and productivity product by <a href="http://www.lyndir.com" onclick="_gaq.push(['_trackPageview', '/outbound/lyndir.com']);">Lyndir</a>, © 2011.
|
||||
Master Password is a security and productivity product by <a href="http://www.lyndir.com" rel="author" onclick="_gaq.push(['_trackPageview', '/outbound/lyndir.com']);">Lyndir</a>, © 2011.
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
|
||||
1090
Site/css/buttons/buttons.css
Executable file
114
Site/css/buttons/demo.html
Executable file
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" href="buttons.css" type="text/css" charset="utf-8" />
|
||||
|
||||
<style type="text/css">
|
||||
body{
|
||||
background: #fff;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 30px 10px;
|
||||
color: #333;
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul li {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
margin: 0 20px 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<title>CSS Button Kit</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>Small Buttons</h1>
|
||||
|
||||
<ul class="smallbuttons">
|
||||
<li><a href="#" class="btn_smallwhite">Button</a></li>
|
||||
<li><a href="#" class="btn_smallblack">Button</a></li>
|
||||
<li><a href="#" class="btn_smallblue">Button</a></li>
|
||||
<li><a href="#" class="btn_smallgreen">Button</a></li>
|
||||
<li><a href="#" class="btn_smallred">Button</a></li>
|
||||
<li><a href="#" class="btn_smallorange">Button</a></li>
|
||||
<li><a href="#" class="btn_smallpurple">Button</a></li>
|
||||
</ul>
|
||||
|
||||
<br class="clear" />
|
||||
|
||||
<ul class="smallbuttons">
|
||||
<li><a href="#" class="btn_smallwhite ico_action"> Action</a></li>
|
||||
<li><a href="#" class="btn_smallblack ico_box"> Download</a></li>
|
||||
<li><a href="#" class="btn_smallblue ico_email"> Email</a></li>
|
||||
<li><a href="#" class="btn_smallgreen ico_folder"> Folder</a></li>
|
||||
<li><a href="#" class="btn_smallred ico_heart"> Like</a></li>
|
||||
<li><a href="#" class="btn_smallorange ico_collection"> Collections</a></li>
|
||||
<li><a href="#" class="btn_smallpurple ico_eye"> View</a></li>
|
||||
</ul>
|
||||
|
||||
<br class="clear" />
|
||||
|
||||
<h1>Medium Buttons</h1>
|
||||
|
||||
<ul class="mediumbuttons">
|
||||
<li><a href="#" class="btn_mediumwhite"> Button</a></li>
|
||||
<li><a href="#" class="btn_mediumblack">Button</a></li>
|
||||
<li><a href="#" class="btn_mediumgray1">Button</a></li>
|
||||
<li><a href="#" class="btn_mediumgray2">Button</a></li>
|
||||
<li><a href="#" class="btn_mediumgray3">Button</a></li>
|
||||
</ul>
|
||||
|
||||
<br class="clear" />
|
||||
|
||||
<ul class="mediumbuttons">
|
||||
<li><a href="#" class="btn_mediumwhite ico_file"> Document</a></li>
|
||||
<li><a href="#" class="btn_mediumblack ico_chart"> Stats</a></li>
|
||||
<li><a href="#" class="btn_mediumgray1 ico_locked"> Lock</a></li>
|
||||
<li><a href="#" class="btn_mediumgray2 ico_music"> Music</a></li>
|
||||
<li><a href="#" class="btn_mediumgray3 ico_print"> Print</a></li>
|
||||
</ul>
|
||||
|
||||
<br class="clear" />
|
||||
|
||||
<h1>Large Buttons</h1>
|
||||
|
||||
<ul class="largebuttons">
|
||||
<li><a href="#" class="btn_largewhite">Button</a></li>
|
||||
<li><a href="#" class="btn_largeblack">Button</a></li>
|
||||
<li><a href="#" class="btn_largegray">Button</a></li>
|
||||
<li><a href="#" class="btn_largeblue">Button</a></li>
|
||||
<li><a href="#" class="btn_largered">Button</a></li>
|
||||
<li><a href="#" class="btn_largegreen">Button</a></li>
|
||||
</ul>
|
||||
|
||||
<br class="clear" /><br/>
|
||||
|
||||
<ul class="largebuttons">
|
||||
<li><a href="#" class="btn_largewhite ico_playvideo"> Watch</a></li>
|
||||
<li><a href="#" class="btn_largeblack ico_search"> Search</a></li>
|
||||
<li><a href="#" class="btn_largegray ico_tel"> Call</a></li>
|
||||
<li><a href="#" class="btn_largeblue ico_user"> Login</a></li>
|
||||
<li><a href="#" class="btn_largered ico_settings1"> Settings</a></li>
|
||||
<li><a href="#" class="btn_largegreen ico_link"> Link</a></li>
|
||||
</ul>
|
||||
|
||||
<br class="clear" />
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
Site/css/buttons/signifylite-webfont.eot
Executable file
141
Site/css/buttons/signifylite-webfont.svg
Executable file
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
This is a custom SVG webfont generated by Font Squirrel.
|
||||
Copyright : Copyright MediaLoot 2011
|
||||
Designer : Tony Thomas
|
||||
Foundry : MediaLoot
|
||||
Foundry URL : httpwwwmedialootcom
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="SignifyLiteRegular" horiz-adv-x="2048" >
|
||||
<font-face units-per-em="2048" ascent="1638" descent="-410" />
|
||||
<missing-glyph horiz-adv-x="512" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode="	" horiz-adv-x="512" />
|
||||
<glyph unicode=" " horiz-adv-x="512" />
|
||||
<glyph unicode="!" />
|
||||
<glyph unicode=""" />
|
||||
<glyph unicode="#" />
|
||||
<glyph unicode="$" />
|
||||
<glyph unicode="%" />
|
||||
<glyph unicode="&" />
|
||||
<glyph unicode="'" />
|
||||
<glyph unicode="(" />
|
||||
<glyph unicode=")" />
|
||||
<glyph unicode="*" />
|
||||
<glyph unicode="+" />
|
||||
<glyph unicode="," />
|
||||
<glyph unicode="-" />
|
||||
<glyph unicode="." />
|
||||
<glyph unicode="/" />
|
||||
<glyph unicode="0" />
|
||||
<glyph unicode="1" horiz-adv-x="1816" d="M121 786l788 787l787 -787h-492v-786h-590v786h-493z" />
|
||||
<glyph unicode="2" horiz-adv-x="1734" d="M80 494v589h788v492l787 -787l-787 -786v492h-788z" />
|
||||
<glyph unicode="3" horiz-adv-x="1742" d="M84 782h491v787h590v-787h494l-787 -786z" />
|
||||
<glyph unicode="4" horiz-adv-x="1794" d="M111 793l786 786v-492h786v-589h-786v-492z" />
|
||||
<glyph unicode="5" horiz-adv-x="1429" d="M113 8v1204l1204 -604z" />
|
||||
<glyph unicode="6" horiz-adv-x="1574" d="M147 4v1278h482v-1278h-482zM946 4v1278h481v-1278h-481z" />
|
||||
<glyph unicode="7" horiz-adv-x="1591" d="M115 -8v1360h1362v-1360h-1362z" />
|
||||
<glyph unicode="8" d="M184 16v240v360h240v-360h960v295l388 -387l-388 -387v239h-960h-240zM276 1067l388 389v-242h960h240v-239v-359h-240v359h-960v-295z" />
|
||||
<glyph unicode="9" horiz-adv-x="1677" d="M162 481v1035h1034l-311 -312q14 -6 40.5 -19.5t99.5 -65.5t138.5 -117.5t141 -178t125 -243.5t73 -319.5t2.5 -399.5q-25 47 -69.5 121.5t-173.5 260t-250 315.5t-272.5 210t-268.5 23z" />
|
||||
<glyph unicode=":" />
|
||||
<glyph unicode=";" />
|
||||
<glyph unicode="<" horiz-adv-x="1931" d="M160 707l919 708v-534l693 534v-1417l-693 534v-534z" />
|
||||
<glyph unicode="=" />
|
||||
<glyph unicode=">" horiz-adv-x="1976" d="M182 -6v1417l692 -534v534l920 -709l-920 -708v534z" />
|
||||
<glyph unicode="?" />
|
||||
<glyph unicode="@" />
|
||||
<glyph unicode="A" horiz-adv-x="2029" d="M111 -2v227v905v113v113h790v-226h-565v-905h1358v226h225v-226v-227h-225h-1583zM1024 315q-6 68 -6 133q0 415 262 672q98 96 188 135l-208 215h659v-718l-184 219q-35 17 -74 17q-26 0 -52 -8q-67 -19 -135.5 -75.5t-134 -130.5t-123 -154.5t-100.5 -148.5t-67 -111z " />
|
||||
<glyph unicode="B" horiz-adv-x="1792" d="M154 6v1010h1484v-1010h-1484zM154 1130l227 343h438v-343h-665zM973 1130v343h438l227 -343h-665z" />
|
||||
<glyph unicode="C" horiz-adv-x="1863" d="M129 811q0 334 235.5 568.5t567.5 234.5t567.5 -234.5t235.5 -568.5q0 -332 -235.5 -567.5t-567.5 -235.5t-567.5 235.5t-235.5 567.5zM330 811q0 -250 177 -426t425 -176t425 176t177 426t-177 426t-425 176t-425 -176t-177 -426zM590 819q0 190 100 291 q100 102 285 102q150 0 227 -57q82 -57 123 -180l-207 -47q-12 35 -24 51q-18 27 -47 43q-29 14 -66 14q-80 0 -123 -65q-33 -49 -33 -150q0 -125 39 -174q39 -47 109 -47q68 0 102 37q35 39 51 111l207 -62q-20 -84 -65 -145q-49 -61 -113 -88q-66 -31 -168 -31 q-125 0 -205 37q-80 35 -135 127q-57 90 -57 233z" />
|
||||
<glyph unicode="D" horiz-adv-x="1900" d="M152 793q0 330 234.5 564t563.5 234q190 0 348 -77.5t253.5 -198.5t146.5 -257t51 -265q0 -332 -234.5 -565.5t-564.5 -233.5q-332 0 -565 236.5t-233 562.5zM352 793q0 -193 111 -349l833 836q-156 111 -346 111q-248 0 -423 -175t-175 -423zM604 303q156 -111 346 -110 q248 0 423 176t175 424q0 190 -110 346z" />
|
||||
<glyph unicode="E" horiz-adv-x="2426" d="M106 14v1420v104h2214v-1524h-2214zM244 256l618 573l-618 465v-1038zM309 1399l924 -676l924 676h-1848zM324 154h1736l-563 618l-268 -199l-266 189zM1587 834l596 -641v1101z" />
|
||||
<glyph unicode="F" horiz-adv-x="2351" d="M170 10v1094h2011v-1094h-2011zM170 1233v88l88 262h786l89 -262h1048v-88h-2011z" />
|
||||
<glyph unicode="G" horiz-adv-x="2177" d="M166 -6l2 971l342 872h1149l350 -872l2 -971h-1845zM342 1010h1495l-252 710h-999zM467 1128v80h1227v-80h-1227zM569 1331v80h1039v-80h-1039zM643 1526v80h889v-80h-889zM756 381h665v237h-665v-237z" />
|
||||
<glyph unicode="H" horiz-adv-x="1933" d="M133 1102q0 178 137.5 303t331.5 125q223 0 365 -162q141 162 364 162q195 0 332 -125t137 -303q0 -10 -2 -30.5t-18.5 -86t-45 -134.5t-91 -172.5t-146.5 -203.5t-222 -224t-308 -241q-233 160 -405.5 331t-250.5 301t-123 237.5t-49 164.5z" />
|
||||
<glyph unicode="I" horiz-adv-x="2330" d="M121 758q348 365 842 465q123 25 202 24q23 0 65 -3t167 -29.5t244.5 -72.5t278.5 -146.5t290 -237.5q-131 -137 -287 -238.5t-285 -147.5t-238.5 -73t-170.5 -29l-64 -4q-23 0 -64.5 3t-166.5 30t-245 73t-278.5 147.5t-289.5 238.5zM774 758q0 -154 113.5 -261.5 t277.5 -107.5t277.5 107.5t113.5 261.5q0 152 -114.5 260t-276 108t-276.5 -108t-115 -260zM1034 758q0 51 39 87t92.5 36t92 -36t38.5 -87t-38.5 -87t-92 -36t-92.5 36t-39 87z" />
|
||||
<glyph unicode="J" horiz-adv-x="1712" d="M154 4v1182l577 629h828v-1811h-1405zM307 154h1118v1525h-610v-589h-508v-936z" />
|
||||
<glyph unicode="K" horiz-adv-x="2177" d="M168 119v526q0 55 38 94t93 39h262q55 0 94 -39t39 -94v-526q0 -53 -39 -92t-94 -39h-262q-55 0 -93 39t-38 92zM825 119v1581q0 53 39 92t92 39h265q53 0 92 -39t39 -92v-1581q0 -53 -39 -92t-92 -39h-265q-53 0 -92 39t-39 92zM1483 119v921q0 55 39 94.5t94 39.5h262 q55 0 93 -39t38 -95v-921q0 -53 -38 -92t-93 -39h-262q-55 0 -94 39t-39 92z" />
|
||||
<glyph unicode="L" horiz-adv-x="1728" d="M188 104v1012q0 47 34 81t79 34h21q16 209 170.5 353.5t365.5 144.5t364.5 -144.5t170.5 -353.5h32q45 0 80 -34t35 -81v-1012q0 -47 -33.5 -81.5t-81.5 -34.5h-1124q-45 0 -79 34.5t-34 81.5zM469 1231h776q-16 147 -126.5 248.5t-260.5 101.5q-152 0 -262.5 -101.5 t-126.5 -248.5zM680 670q0 -106 100 -168v-226h197v226q96 59 96 168q0 82 -58.5 139t-140.5 57q-80 0 -137 -57t-57 -139z" />
|
||||
<glyph unicode="M" horiz-adv-x="1972" d="M147 293q0 123 110 209t267 86q160 0 160 -27v778v269q0 23 16.5 38t38.5 15h1030q23 0 39.5 -15.5t16.5 -37.5v-269v-1073h-2q-16 -94 -107.5 -181t-226.5 -87q-156 0 -265.5 87t-109.5 208q0 123 109.5 209t265.5 86q162 0 162 -27v692h-793v-960v-27h-2 q-14 -94 -106.5 -181t-225.5 -87q-156 0 -266.5 87t-110.5 208z" />
|
||||
<glyph unicode="N" horiz-adv-x="2121" d="M166 309v383v193v-127v127h256v770h127h1282h127v-129v-1409v-129h-127h-1409v8q-111 23 -183.5 109.5t-72.5 203.5zM293 309q0 -61 36 -110t93 -70v-12h1409v1409h-1282v-1280h-127v512h-129v-66v-190v-193zM678 246v127h897v-127h-897zM678 502v127h768v-127h-768z M678 758v127h1024v-127h-1024zM678 1014v383h512v-383h-512zM1319 1014v127h256v-127h-256zM1319 1270v127h383v-127h-383z" />
|
||||
<glyph unicode="O" horiz-adv-x="1900" d="M119 834q0 346 243.5 588.5t587.5 242.5t588 -242.5t244 -588.5q0 -344 -244 -588t-588 -244t-587.5 244t-243.5 588zM258 813q14 -41 37.5 -81t57.5 -91t48 -76q10 -14 15.5 -32.5t4.5 -29.5l-3 -35q-2 -24 -2 -32v-2q-1 -6 -1 -11q0 -38 25 -63q14 -18 45 -48.5 t52.5 -57.5t31.5 -57l4 -2q68 -41 170 -74q6 0 24.5 7t78.5 11v2q0 8 20.5 19.5t52 27t46.5 25.5q19 22 18 43q0 28 -36 52q-43 29 -83 29q-18 0 -35 -5q-25 16 -96 80.5t-121 85.5q-23 20 -64.5 33.5t-75.5 20.5t-59.5 31.5t-25.5 67.5q-4 16 0 24.5t-5 22t-10 17.5 t-13.5 15t-15.5 14.5t-17.5 13.5t-16.5 12q-4 12 -3 23q0 38 44 59q22 10 41 10q28 0 47 -22q16 -16 17 -43q29 2 69.5 56t73.5 60q12 10 44 13.5t47.5 10.5t0.5 34q8 8 32 18l38 17q14 6 22.5 24.5t-12.5 42.5q-12 55 -60 69q-9 3 -18 2q-35 0 -53 -46q-8 -3 -15 -3 q-26 0 -30 38v10q0 48 35 74q18 14 43 8q27 -2 38 10t4 27.5t-26 23.5q0 6 -4 2q-16 6 -20 4q-25 16 -22 44t18.5 54.5t14.5 53t-36 39.5q-285 -244 -358 -396q-24 -71 -25 -201q0 -34 2 -73zM449 702q-7 -3 -8 -7q0 -9 24 -23q16 -20 35 -15q8 31 -25 39v2q-2 2 -7 2t-7 2 h-12zM590 909l2 -4h2q6 2 10 6q-8 0 -14 -2zM725 1546q38 3 74 3q136 0 223 -44q8 -27 36.5 -35t57.5 -5t50.5 -8t19.5 -40l4 -4l2 -10q2 -2 2 4q50 -50 50 -69q0 -11 -18 -11h-5h-4q-35 4 -46 -30q-4 -14 -4 -29q0 -22 9 -46q14 -42 41 -55q25 -10 41 -4t13 25.5t-28 32.5 q-2 23 12 22q27 -2 52 -43q10 -18 10 -31q0 -16 -14 -26q-4 -4 -7 -4l-2 -2q-37 -27 -40 -114t-9 -103q-9 -19 -9 -32q0 -20 26 -22h4q36 0 57 35q10 18 4 41q-8 23 6.5 35t36 15t35.5 10.5t4 23.5q21 -11 35 -11q27 0 31 40q1 9 1 17q0 42 -26 69q10 43 33 16q20 -29 6 -59 q2 -10 5 -16.5t10 -12.5t11.5 -8t15.5 -6t13 -6q2 0 4 -2q20 -8 21 -27q0 -13 -10 -31q-24 -81 -116 -81q-27 0 -61 7q-31 -12 -51 -40.5t-38.5 -68.5t-30.5 -58q-44 -80 -44 -140q0 -58 42 -98q25 -23 51 -28t54 6.5t47.5 23.5t45.5 33q10 1 19 1q57 0 71 -59q5 -19 5 -35 q0 -45 -31 -81q-4 -16 -4 -19q180 211 180 482l-6 86q-7 -4 -15 -5q-23 0 -49 37q-20 29 -6 27q8 0 29 -16q20 4 30 12q-12 70 -36 135l-11.5 12t-9.5 11q0 4 -1 12t-1 14v2q-92 188 -273.5 304t-398.5 116q-41 0 -61 -2q-20 -4 -33 -6h-12q-66 -11 -119 -27zM752 1201 q-3 -15 -4 -27q0 -24 14 -33q20 8 29.5 39.5t2 59.5t-29.5 26q-2 0 -6 -1t-6 -1q10 -17 0 -63zM762 973q-10 -2 -10 -7q0 -7 20 -20h16q2 -2 -4 -8q25 18 11.5 28.5t-33.5 6.5zM862 1331l6 6q18 8 11 23q-2 4 -11 4q-2 2 -8 2q-2 0 -5 -1t-5 -1q-21 -4 -21 -21q0 -33 33 -12 zM853 1315q-9 -13 -9 -27q0 -20 18 -41q18 -25 6 -53.5t-11 -59t36 -40.5q10 25 42 64.5t48 74.5q10 21 10 44q0 14 -4 30q-11 46 -55 46q-7 0 -14 -1q-45 -6 -67 -37zM1196 1087q-8 0 -8 -4q0 -6 14 -20q-12 -31 15 -33q18 0 30 19q2 16 -17 28q-17 11 -30 11q-2 -1 -4 -1z " />
|
||||
<glyph unicode="P" horiz-adv-x="2043" d="M160 322v968h432v215v109h106h754v-109v-215h432v-968h-432v-109h-106v-109v-106h-648h-106v106v218h-432zM483 537h109v215h106v-215v-215v-218h432v218h107h109v215v215h106v-215h217v323h-1186v-323zM698 1290h648v215h-648v-215zM1452 1075h217v109h-217v-109z" />
|
||||
<glyph unicode="Q" horiz-adv-x="2095" d="M152 334v1122q0 47 32.5 79t77.5 32h1571q47 0 79 -32t32 -79v-1122q0 -47 -32 -79t-79 -32h-1571q-45 0 -77.5 32t-32.5 79zM375 446h1345v897h-1345v-897zM487 55.5q0 22.5 16.5 39t39.5 16.5h1009q23 0 39.5 -16.5t16.5 -39t-16.5 -40t-39.5 -17.5h-1009 q-23 0 -39.5 17.5t-16.5 40zM823 559v672l449 -336z" />
|
||||
<glyph unicode="R" horiz-adv-x="1847" d="M125 811q0 330 234.5 564.5t564 234.5t564 -234.5t234.5 -564.5q0 -332 -234.5 -565.5t-564 -233.5t-564 233.5t-234.5 565.5zM326 811q0 -248 175 -424t422.5 -176t423 176t175.5 424t-175.5 423t-423 175t-422.5 -175t-175 -423zM625 512v598h336q90 0 143 -16 q51 -14 80 -60q31 -43 30 -106q0 -53 -22 -94q-27 -41 -64 -64q-29 -16 -71 -25q33 -10 53 -24q23 -23 31 -35q25 -33 28 -41l54 -133h-199l-92 143q-31 47 -37 52q-20 14 -49 14h-19l-4 -209h-198zM823 811h86q18 0 54 8q20 6 32 21q12 18 13 104q0 35 -21 49 q-16 16 -74 17h-90v-199z" />
|
||||
<glyph unicode="S" horiz-adv-x="1953" d="M137 993.5q0 270.5 190.5 461t460.5 190.5q268 0 460 -190.5t192 -461.5q0 -188 -101 -344q20 -10 39 -26l406 -406q33 -33 33 -80t-33 -82q-33 -33 -82 -32.5t-82 32.5l-406 406q-10 12 -16 26q-182 -145 -410 -145q-270 0 -460.5 190.5t-190.5 461zM338 993.5 q0 -186.5 132 -318.5t318 -132q160 0 283 100l57 47l41 66q70 111 70 237q0 186 -132 318.5t-318.5 132.5t-318.5 -132t-132 -318.5z" />
|
||||
<glyph unicode="T" horiz-adv-x="2066" d="M164 1642l196 197l494 -491l-197 -197l-82 82q2 -74 75 -204t210 -267q215 -215 461 -293l25 -8l-99 98l199 197l491 -492l-196 -198q-72 -72 -187 -73q-113 0 -267 70q-310 141 -646 480q-303 303 -436 620q-77 184 -77 315q0 94 40 160z" />
|
||||
<glyph unicode="U" horiz-adv-x="2420" d="M160 -41v356h2q0 2 -2 7v4q0 37 20 73q6 10 65.5 42t126.5 63l126 57q59 27 61 29q57 27 151.5 59.5t155 61t92.5 67.5q-8 76 -84.5 256t-82.5 291l-2 42q0 177 79 312q88 152 258 213h84h84q170 -61 258 -213q79 -136 79 -312q0 -21 -1 -42q-6 -111 -83 -291t-85 -256 q33 -39 93.5 -67.5t154.5 -61.5t152 -59q2 -2 61 -29l126 -57q67 -31 126 -62.5t66 -42.5q20 -37 20 -73v-4q-2 -4 -2 -7h2v-356h-20h-1031h-1030h-20z" />
|
||||
<glyph unicode="V" horiz-adv-x="1996" d="M139 6v1837h1719v-1837h-1719zM225 80h172v145h-172v-145zM225 299h172v147h-172v-147zM225 520h172v148h-172v-148zM225 741h172v146h-172v-146zM225 961h172v147h-172v-147zM225 1182h172v147h-172v-147zM225 1401h172v147h-172v-147zM225 1622h172v147h-172v-147z M483 80h1030v807h-1030v-807zM483 961h1030v808h-1030v-808zM1599 80h173v145h-173v-145zM1599 299h173v147h-173v-147zM1599 520h173v148h-173v-148zM1599 741h173v146h-173v-146zM1599 961h173v147h-173v-147zM1599 1182h173v147h-173v-147zM1599 1401h173v147h-173v-147z M1599 1622h173v147h-173v-147z" />
|
||||
<glyph unicode="W" horiz-adv-x="2418" d="M82 1130q229 252 529 383.5t630 131.5q315 0 595 -136.5t501 -378.5l-213 -233q-383 449 -889 449q-264 0 -505 -117t-435 -332zM434 743q152 166 354.5 253t422 87t421 -87t353.5 -253l-213 -233q-270 299 -562 299q-295 0 -565 -299zM961 278.5q0 112.5 73 194.5 q72 80 175.5 80t174.5 -80q74 -82 74 -194.5t-74 -192.5q-72 -82 -175 -82t-175 82q-73 80 -73 192.5z" />
|
||||
<glyph unicode="X" horiz-adv-x="1931" d="M152 229l585 584l-585 586l229 227l586 -584l583 584l230 -227l-586 -586l586 -584l-230 -229l-583 584l-586 -584z" />
|
||||
<glyph unicode="Y" horiz-adv-x="2101" d="M150 1364l288 -287l342 90l92 344l-286 287q36 5 71 5q80 0 158 -25q113 -37 199 -123q141 -141 147 -340l-176 -178q-53 -53 -53 -127q0 -41 16 -80l-131 -131l-10 10q-73 -24 -150 -24q-59 0 -119 14q-140 33 -243 135q-86 86 -124 200q-26 79 -26 160q0 35 5 70z M268 -6l121 362l174 19l549 549l-43 43q-16 16 -16 43q0 25 16 41l80 79l516 519q18 16 44 16t42 -16l170 -172q18 -16 18 -42t-18 -42l-596 -598q-16 -16 -42 -16.5t-44 16.5l-41 43l-211 -209l-338 -340l-20 -174zM1073 543l131 133q37 -18 78 -18q15 0 31 2q57 9 98 51 l80 80l369 -369q10 -8 26 -25q70 -70 70 -168t-69.5 -167.5t-168 -69.5t-168.5 69q-10 10 -22 27zM1587 260q0 -43 29 -72q31 -31 73 -30.5t70 30.5q31 29 31 72t-31 72q-29 29 -70.5 28.5t-72.5 -28.5q-29 -29 -29 -72z" />
|
||||
<glyph unicode="Z" horiz-adv-x="1525" d="M182 -23l582 928h-465l1044 928l-464 -696h464z" />
|
||||
<glyph unicode="[" />
|
||||
<glyph unicode="\" />
|
||||
<glyph unicode="]" />
|
||||
<glyph unicode="^" />
|
||||
<glyph unicode="_" />
|
||||
<glyph unicode="`" />
|
||||
<glyph unicode="a" />
|
||||
<glyph unicode="b" />
|
||||
<glyph unicode="c" />
|
||||
<glyph unicode="d" />
|
||||
<glyph unicode="e" />
|
||||
<glyph unicode="f" />
|
||||
<glyph unicode="g" />
|
||||
<glyph unicode="h" />
|
||||
<glyph unicode="i" />
|
||||
<glyph unicode="j" />
|
||||
<glyph unicode="k" />
|
||||
<glyph unicode="l" />
|
||||
<glyph unicode="m" />
|
||||
<glyph unicode="n" />
|
||||
<glyph unicode="o" />
|
||||
<glyph unicode="p" />
|
||||
<glyph unicode="q" />
|
||||
<glyph unicode="r" />
|
||||
<glyph unicode="s" />
|
||||
<glyph unicode="t" />
|
||||
<glyph unicode="u" />
|
||||
<glyph unicode="v" />
|
||||
<glyph unicode="w" />
|
||||
<glyph unicode="x" />
|
||||
<glyph unicode="y" />
|
||||
<glyph unicode="z" />
|
||||
<glyph unicode="{" />
|
||||
<glyph unicode="|" />
|
||||
<glyph unicode="}" />
|
||||
<glyph unicode="~" horiz-adv-x="1720" d="M204 393.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM400 411.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM735 1117.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5z" />
|
||||
<glyph unicode="­" />
|
||||
<glyph unicode="²" horiz-adv-x="1734" d="M80 494v589h788v492l787 -787l-787 -786v492h-788z" />
|
||||
<glyph unicode="³" horiz-adv-x="1742" d="M84 782h491v787h590v-787h494l-787 -786z" />
|
||||
<glyph unicode="¹" horiz-adv-x="1816" d="M121 786l788 787l787 -787h-492v-786h-590v786h-493z" />
|
||||
<glyph unicode="À" horiz-adv-x="2029" d="M111 -2v227v905v113v113h790v-226h-565v-905h1358v226h225v-226v-227h-225h-1583zM1024 315q-6 68 -6 133q0 415 262 672q98 96 188 135l-208 215h659v-718l-184 219q-35 17 -74 17q-26 0 -52 -8q-67 -19 -135.5 -75.5t-134 -130.5t-123 -154.5t-100.5 -148.5t-67 -111z " />
|
||||
<glyph unicode="Â" horiz-adv-x="2029" d="M111 -2v227v905v113v113h790v-226h-565v-905h1358v226h225v-226v-227h-225h-1583zM1024 315q-6 68 -6 133q0 415 262 672q98 96 188 135l-208 215h659v-718l-184 219q-35 17 -74 17q-26 0 -52 -8q-67 -19 -135.5 -75.5t-134 -130.5t-123 -154.5t-100.5 -148.5t-67 -111z " />
|
||||
<glyph unicode="Ã" horiz-adv-x="2029" d="M933 2175.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM1129 2193.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM1464 2899.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5zM111 -2v227v905v113v113h790v-226h-565v-905h1358v226h225v-226v-227h-225h-1583zM1024 315q-6 68 -6 133q0 415 262 672q98 96 188 135l-208 215h659v-718l-184 219q-35 17 -74 17q-26 0 -52 -8q-67 -19 -135.5 -75.5t-134 -130.5t-123 -154.5 t-100.5 -148.5t-67 -111z" />
|
||||
<glyph unicode="È" horiz-adv-x="2426" d="M106 14v1420v104h2214v-1524h-2214zM244 256l618 573l-618 465v-1038zM309 1399l924 -676l924 676h-1848zM324 154h1736l-563 618l-268 -199l-266 189zM1587 834l596 -641v1101z" />
|
||||
<glyph unicode="Ê" horiz-adv-x="2426" d="M106 14v1420v104h2214v-1524h-2214zM244 256l618 573l-618 465v-1038zM309 1399l924 -676l924 676h-1848zM324 154h1736l-563 618l-268 -199l-266 189zM1587 834l596 -641v1101z" />
|
||||
<glyph unicode="Ì" horiz-adv-x="2330" d="M121 758q348 365 842 465q123 25 202 24q23 0 65 -3t167 -29.5t244.5 -72.5t278.5 -146.5t290 -237.5q-131 -137 -287 -238.5t-285 -147.5t-238.5 -73t-170.5 -29l-64 -4q-23 0 -64.5 3t-166.5 30t-245 73t-278.5 147.5t-289.5 238.5zM774 758q0 -154 113.5 -261.5 t277.5 -107.5t277.5 107.5t113.5 261.5q0 152 -114.5 260t-276 108t-276.5 -108t-115 -260zM1034 758q0 51 39 87t92.5 36t92 -36t38.5 -87t-38.5 -87t-92 -36t-92.5 36t-39 87z" />
|
||||
<glyph unicode="Î" horiz-adv-x="2330" d="M121 758q348 365 842 465q123 25 202 24q23 0 65 -3t167 -29.5t244.5 -72.5t278.5 -146.5t290 -237.5q-131 -137 -287 -238.5t-285 -147.5t-238.5 -73t-170.5 -29l-64 -4q-23 0 -64.5 3t-166.5 30t-245 73t-278.5 147.5t-289.5 238.5zM774 758q0 -154 113.5 -261.5 t277.5 -107.5t277.5 107.5t113.5 261.5q0 152 -114.5 260t-276 108t-276.5 -108t-115 -260zM1034 758q0 51 39 87t92.5 36t92 -36t38.5 -87t-38.5 -87t-92 -36t-92.5 36t-39 87z" />
|
||||
<glyph unicode="Ñ" horiz-adv-x="2121" d="M534 2175.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM730 2193.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM1065 2899.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5zM166 309v383v193v-127v127h256v770h127h1282h127v-129v-1409v-129h-127h-1409v8q-111 23 -183.5 109.5t-72.5 203.5zM293 309q0 -61 36 -110t93 -70v-12h1409v1409h-1282v-1280h-127v512h-129v-66v-190v-193zM678 246v127h897v-127h-897zM678 502v127 h768v-127h-768zM678 758v127h1024v-127h-1024zM678 1014v383h512v-383h-512zM1319 1014v127h256v-127h-256zM1319 1270v127h383v-127h-383z" />
|
||||
<glyph unicode="Ò" horiz-adv-x="1900" d="M119 834q0 346 243.5 588.5t587.5 242.5t588 -242.5t244 -588.5q0 -344 -244 -588t-588 -244t-587.5 244t-243.5 588zM258 813q14 -41 37.5 -81t57.5 -91t48 -76q10 -14 15.5 -32.5t4.5 -29.5l-3 -35q-2 -24 -2 -32v-2q-1 -6 -1 -11q0 -38 25 -63q14 -18 45 -48.5 t52.5 -57.5t31.5 -57l4 -2q68 -41 170 -74q6 0 24.5 7t78.5 11v2q0 8 20.5 19.5t52 27t46.5 25.5q19 22 18 43q0 28 -36 52q-43 29 -83 29q-18 0 -35 -5q-25 16 -96 80.5t-121 85.5q-23 20 -64.5 33.5t-75.5 20.5t-59.5 31.5t-25.5 67.5q-4 16 0 24.5t-5 22t-10 17.5 t-13.5 15t-15.5 14.5t-17.5 13.5t-16.5 12q-4 12 -3 23q0 38 44 59q22 10 41 10q28 0 47 -22q16 -16 17 -43q29 2 69.5 56t73.5 60q12 10 44 13.5t47.5 10.5t0.5 34q8 8 32 18l38 17q14 6 22.5 24.5t-12.5 42.5q-12 55 -60 69q-9 3 -18 2q-35 0 -53 -46q-8 -3 -15 -3 q-26 0 -30 38v10q0 48 35 74q18 14 43 8q27 -2 38 10t4 27.5t-26 23.5q0 6 -4 2q-16 6 -20 4q-25 16 -22 44t18.5 54.5t14.5 53t-36 39.5q-285 -244 -358 -396q-24 -71 -25 -201q0 -34 2 -73zM449 702q-7 -3 -8 -7q0 -9 24 -23q16 -20 35 -15q8 31 -25 39v2q-2 2 -7 2t-7 2 h-12zM590 909l2 -4h2q6 2 10 6q-8 0 -14 -2zM725 1546q38 3 74 3q136 0 223 -44q8 -27 36.5 -35t57.5 -5t50.5 -8t19.5 -40l4 -4l2 -10q2 -2 2 4q50 -50 50 -69q0 -11 -18 -11h-5h-4q-35 4 -46 -30q-4 -14 -4 -29q0 -22 9 -46q14 -42 41 -55q25 -10 41 -4t13 25.5t-28 32.5 q-2 23 12 22q27 -2 52 -43q10 -18 10 -31q0 -16 -14 -26q-4 -4 -7 -4l-2 -2q-37 -27 -40 -114t-9 -103q-9 -19 -9 -32q0 -20 26 -22h4q36 0 57 35q10 18 4 41q-8 23 6.5 35t36 15t35.5 10.5t4 23.5q21 -11 35 -11q27 0 31 40q1 9 1 17q0 42 -26 69q10 43 33 16q20 -29 6 -59 q2 -10 5 -16.5t10 -12.5t11.5 -8t15.5 -6t13 -6q2 0 4 -2q20 -8 21 -27q0 -13 -10 -31q-24 -81 -116 -81q-27 0 -61 7q-31 -12 -51 -40.5t-38.5 -68.5t-30.5 -58q-44 -80 -44 -140q0 -58 42 -98q25 -23 51 -28t54 6.5t47.5 23.5t45.5 33q10 1 19 1q57 0 71 -59q5 -19 5 -35 q0 -45 -31 -81q-4 -16 -4 -19q180 211 180 482l-6 86q-7 -4 -15 -5q-23 0 -49 37q-20 29 -6 27q8 0 29 -16q20 4 30 12q-12 70 -36 135l-11.5 12t-9.5 11q0 4 -1 12t-1 14v2q-92 188 -273.5 304t-398.5 116q-41 0 -61 -2q-20 -4 -33 -6h-12q-66 -11 -119 -27zM752 1201 q-3 -15 -4 -27q0 -24 14 -33q20 8 29.5 39.5t2 59.5t-29.5 26q-2 0 -6 -1t-6 -1q10 -17 0 -63zM762 973q-10 -2 -10 -7q0 -7 20 -20h16q2 -2 -4 -8q25 18 11.5 28.5t-33.5 6.5zM862 1331l6 6q18 8 11 23q-2 4 -11 4q-2 2 -8 2q-2 0 -5 -1t-5 -1q-21 -4 -21 -21q0 -33 33 -12 zM853 1315q-9 -13 -9 -27q0 -20 18 -41q18 -25 6 -53.5t-11 -59t36 -40.5q10 25 42 64.5t48 74.5q10 21 10 44q0 14 -4 30q-11 46 -55 46q-7 0 -14 -1q-45 -6 -67 -37zM1196 1087q-8 0 -8 -4q0 -6 14 -20q-12 -31 15 -33q18 0 30 19q2 16 -17 28q-17 11 -30 11q-2 -1 -4 -1z " />
|
||||
<glyph unicode="Ô" horiz-adv-x="1900" d="M119 834q0 346 243.5 588.5t587.5 242.5t588 -242.5t244 -588.5q0 -344 -244 -588t-588 -244t-587.5 244t-243.5 588zM258 813q14 -41 37.5 -81t57.5 -91t48 -76q10 -14 15.5 -32.5t4.5 -29.5l-3 -35q-2 -24 -2 -32v-2q-1 -6 -1 -11q0 -38 25 -63q14 -18 45 -48.5 t52.5 -57.5t31.5 -57l4 -2q68 -41 170 -74q6 0 24.5 7t78.5 11v2q0 8 20.5 19.5t52 27t46.5 25.5q19 22 18 43q0 28 -36 52q-43 29 -83 29q-18 0 -35 -5q-25 16 -96 80.5t-121 85.5q-23 20 -64.5 33.5t-75.5 20.5t-59.5 31.5t-25.5 67.5q-4 16 0 24.5t-5 22t-10 17.5 t-13.5 15t-15.5 14.5t-17.5 13.5t-16.5 12q-4 12 -3 23q0 38 44 59q22 10 41 10q28 0 47 -22q16 -16 17 -43q29 2 69.5 56t73.5 60q12 10 44 13.5t47.5 10.5t0.5 34q8 8 32 18l38 17q14 6 22.5 24.5t-12.5 42.5q-12 55 -60 69q-9 3 -18 2q-35 0 -53 -46q-8 -3 -15 -3 q-26 0 -30 38v10q0 48 35 74q18 14 43 8q27 -2 38 10t4 27.5t-26 23.5q0 6 -4 2q-16 6 -20 4q-25 16 -22 44t18.5 54.5t14.5 53t-36 39.5q-285 -244 -358 -396q-24 -71 -25 -201q0 -34 2 -73zM449 702q-7 -3 -8 -7q0 -9 24 -23q16 -20 35 -15q8 31 -25 39v2q-2 2 -7 2t-7 2 h-12zM590 909l2 -4h2q6 2 10 6q-8 0 -14 -2zM725 1546q38 3 74 3q136 0 223 -44q8 -27 36.5 -35t57.5 -5t50.5 -8t19.5 -40l4 -4l2 -10q2 -2 2 4q50 -50 50 -69q0 -11 -18 -11h-5h-4q-35 4 -46 -30q-4 -14 -4 -29q0 -22 9 -46q14 -42 41 -55q25 -10 41 -4t13 25.5t-28 32.5 q-2 23 12 22q27 -2 52 -43q10 -18 10 -31q0 -16 -14 -26q-4 -4 -7 -4l-2 -2q-37 -27 -40 -114t-9 -103q-9 -19 -9 -32q0 -20 26 -22h4q36 0 57 35q10 18 4 41q-8 23 6.5 35t36 15t35.5 10.5t4 23.5q21 -11 35 -11q27 0 31 40q1 9 1 17q0 42 -26 69q10 43 33 16q20 -29 6 -59 q2 -10 5 -16.5t10 -12.5t11.5 -8t15.5 -6t13 -6q2 0 4 -2q20 -8 21 -27q0 -13 -10 -31q-24 -81 -116 -81q-27 0 -61 7q-31 -12 -51 -40.5t-38.5 -68.5t-30.5 -58q-44 -80 -44 -140q0 -58 42 -98q25 -23 51 -28t54 6.5t47.5 23.5t45.5 33q10 1 19 1q57 0 71 -59q5 -19 5 -35 q0 -45 -31 -81q-4 -16 -4 -19q180 211 180 482l-6 86q-7 -4 -15 -5q-23 0 -49 37q-20 29 -6 27q8 0 29 -16q20 4 30 12q-12 70 -36 135l-11.5 12t-9.5 11q0 4 -1 12t-1 14v2q-92 188 -273.5 304t-398.5 116q-41 0 -61 -2q-20 -4 -33 -6h-12q-66 -11 -119 -27zM752 1201 q-3 -15 -4 -27q0 -24 14 -33q20 8 29.5 39.5t2 59.5t-29.5 26q-2 0 -6 -1t-6 -1q10 -17 0 -63zM762 973q-10 -2 -10 -7q0 -7 20 -20h16q2 -2 -4 -8q25 18 11.5 28.5t-33.5 6.5zM862 1331l6 6q18 8 11 23q-2 4 -11 4q-2 2 -8 2q-2 0 -5 -1t-5 -1q-21 -4 -21 -21q0 -33 33 -12 zM853 1315q-9 -13 -9 -27q0 -20 18 -41q18 -25 6 -53.5t-11 -59t36 -40.5q10 25 42 64.5t48 74.5q10 21 10 44q0 14 -4 30q-11 46 -55 46q-7 0 -14 -1q-45 -6 -67 -37zM1196 1087q-8 0 -8 -4q0 -6 14 -20q-12 -31 15 -33q18 0 30 19q2 16 -17 28q-17 11 -30 11q-2 -1 -4 -1z " />
|
||||
<glyph unicode="Õ" horiz-adv-x="1900" d="M294 2175.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM490 2193.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM825 2899.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5zM119 834q0 346 243.5 588.5t587.5 242.5t588 -242.5t244 -588.5q0 -344 -244 -588t-588 -244t-587.5 244t-243.5 588zM258 813q14 -41 37.5 -81t57.5 -91t48 -76q10 -14 15.5 -32.5t4.5 -29.5l-3 -35q-2 -24 -2 -32v-2q-1 -6 -1 -11q0 -38 25 -63 q14 -18 45 -48.5t52.5 -57.5t31.5 -57l4 -2q68 -41 170 -74q6 0 24.5 7t78.5 11v2q0 8 20.5 19.5t52 27t46.5 25.5q19 22 18 43q0 28 -36 52q-43 29 -83 29q-18 0 -35 -5q-25 16 -96 80.5t-121 85.5q-23 20 -64.5 33.5t-75.5 20.5t-59.5 31.5t-25.5 67.5q-4 16 0 24.5t-5 22 t-10 17.5t-13.5 15t-15.5 14.5t-17.5 13.5t-16.5 12q-4 12 -3 23q0 38 44 59q22 10 41 10q28 0 47 -22q16 -16 17 -43q29 2 69.5 56t73.5 60q12 10 44 13.5t47.5 10.5t0.5 34q8 8 32 18l38 17q14 6 22.5 24.5t-12.5 42.5q-12 55 -60 69q-9 3 -18 2q-35 0 -53 -46 q-8 -3 -15 -3q-26 0 -30 38v10q0 48 35 74q18 14 43 8q27 -2 38 10t4 27.5t-26 23.5q0 6 -4 2q-16 6 -20 4q-25 16 -22 44t18.5 54.5t14.5 53t-36 39.5q-285 -244 -358 -396q-24 -71 -25 -201q0 -34 2 -73zM449 702q-7 -3 -8 -7q0 -9 24 -23q16 -20 35 -15q8 31 -25 39v2 q-2 2 -7 2t-7 2h-12zM590 909l2 -4h2q6 2 10 6q-8 0 -14 -2zM725 1546q38 3 74 3q136 0 223 -44q8 -27 36.5 -35t57.5 -5t50.5 -8t19.5 -40l4 -4l2 -10q2 -2 2 4q50 -50 50 -69q0 -11 -18 -11h-5h-4q-35 4 -46 -30q-4 -14 -4 -29q0 -22 9 -46q14 -42 41 -55q25 -10 41 -4 t13 25.5t-28 32.5q-2 23 12 22q27 -2 52 -43q10 -18 10 -31q0 -16 -14 -26q-4 -4 -7 -4l-2 -2q-37 -27 -40 -114t-9 -103q-9 -19 -9 -32q0 -20 26 -22h4q36 0 57 35q10 18 4 41q-8 23 6.5 35t36 15t35.5 10.5t4 23.5q21 -11 35 -11q27 0 31 40q1 9 1 17q0 42 -26 69 q10 43 33 16q20 -29 6 -59q2 -10 5 -16.5t10 -12.5t11.5 -8t15.5 -6t13 -6q2 0 4 -2q20 -8 21 -27q0 -13 -10 -31q-24 -81 -116 -81q-27 0 -61 7q-31 -12 -51 -40.5t-38.5 -68.5t-30.5 -58q-44 -80 -44 -140q0 -58 42 -98q25 -23 51 -28t54 6.5t47.5 23.5t45.5 33q10 1 19 1 q57 0 71 -59q5 -19 5 -35q0 -45 -31 -81q-4 -16 -4 -19q180 211 180 482l-6 86q-7 -4 -15 -5q-23 0 -49 37q-20 29 -6 27q8 0 29 -16q20 4 30 12q-12 70 -36 135l-11.5 12t-9.5 11q0 4 -1 12t-1 14v2q-92 188 -273.5 304t-398.5 116q-41 0 -61 -2q-20 -4 -33 -6h-12 q-66 -11 -119 -27zM752 1201q-3 -15 -4 -27q0 -24 14 -33q20 8 29.5 39.5t2 59.5t-29.5 26q-2 0 -6 -1t-6 -1q10 -17 0 -63zM762 973q-10 -2 -10 -7q0 -7 20 -20h16q2 -2 -4 -8q25 18 11.5 28.5t-33.5 6.5zM862 1331l6 6q18 8 11 23q-2 4 -11 4q-2 2 -8 2q-2 0 -5 -1t-5 -1 q-21 -4 -21 -21q0 -33 33 -12zM853 1315q-9 -13 -9 -27q0 -20 18 -41q18 -25 6 -53.5t-11 -59t36 -40.5q10 25 42 64.5t48 74.5q10 21 10 44q0 14 -4 30q-11 46 -55 46q-7 0 -14 -1q-45 -6 -67 -37zM1196 1087q-8 0 -8 -4q0 -6 14 -20q-12 -31 15 -33q18 0 30 19 q2 16 -17 28q-17 11 -30 11q-2 -1 -4 -1z" />
|
||||
<glyph unicode="Ù" horiz-adv-x="2420" d="M160 -41v356h2q0 2 -2 7v4q0 37 20 73q6 10 65.5 42t126.5 63l126 57q59 27 61 29q57 27 151.5 59.5t155 61t92.5 67.5q-8 76 -84.5 256t-82.5 291l-2 42q0 177 79 312q88 152 258 213h84h84q170 -61 258 -213q79 -136 79 -312q0 -21 -1 -42q-6 -111 -83 -291t-85 -256 q33 -39 93.5 -67.5t154.5 -61.5t152 -59q2 -2 61 -29l126 -57q67 -31 126 -62.5t66 -42.5q20 -37 20 -73v-4q-2 -4 -2 -7h2v-356h-20h-1031h-1030h-20z" />
|
||||
<glyph unicode="Û" horiz-adv-x="2420" d="M160 -41v356h2q0 2 -2 7v4q0 37 20 73q6 10 65.5 42t126.5 63l126 57q59 27 61 29q57 27 151.5 59.5t155 61t92.5 67.5q-8 76 -84.5 256t-82.5 291l-2 42q0 177 79 312q88 152 258 213h84h84q170 -61 258 -213q79 -136 79 -312q0 -21 -1 -42q-6 -111 -83 -291t-85 -256 q33 -39 93.5 -67.5t154.5 -61.5t152 -59q2 -2 61 -29l126 -57q67 -31 126 -62.5t66 -42.5q20 -37 20 -73v-4q-2 -4 -2 -7h2v-356h-20h-1031h-1030h-20z" />
|
||||
<glyph unicode="ã" d="M-656 510.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM-460 528.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM-125 1234.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5z" />
|
||||
<glyph unicode="ñ" d="M-656 510.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM-460 528.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM-125 1234.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5z" />
|
||||
<glyph unicode="õ" d="M-656 510.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM-460 528.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM-125 1234.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5z" />
|
||||
<glyph unicode="Œ" horiz-adv-x="4327" d="M2007 14v1420v104h2214v-1524h-2214zM2145 256l618 573l-618 465v-1038zM2210 1399l924 -676l924 676h-1848zM2225 154h1736l-563 618l-268 -199l-266 189zM3488 834l596 -641v1101zM119 834q0 346 243.5 588.5t587.5 242.5t588 -242.5t244 -588.5q0 -344 -244 -588 t-588 -244t-587.5 244t-243.5 588zM258 813q14 -41 37.5 -81t57.5 -91t48 -76q10 -14 15.5 -32.5t4.5 -29.5l-3 -35q-2 -24 -2 -32v-2q-1 -6 -1 -11q0 -38 25 -63q14 -18 45 -48.5t52.5 -57.5t31.5 -57l4 -2q68 -41 170 -74q6 0 24.5 7t78.5 11v2q0 8 20.5 19.5t52 27 t46.5 25.5q19 22 18 43q0 28 -36 52q-43 29 -83 29q-18 0 -35 -5q-25 16 -96 80.5t-121 85.5q-23 20 -64.5 33.5t-75.5 20.5t-59.5 31.5t-25.5 67.5q-4 16 0 24.5t-5 22t-10 17.5t-13.5 15t-15.5 14.5t-17.5 13.5t-16.5 12q-4 12 -3 23q0 38 44 59q22 10 41 10q28 0 47 -22 q16 -16 17 -43q29 2 69.5 56t73.5 60q12 10 44 13.5t47.5 10.5t0.5 34q8 8 32 18l38 17q14 6 22.5 24.5t-12.5 42.5q-12 55 -60 69q-9 3 -18 2q-35 0 -53 -46q-8 -3 -15 -3q-26 0 -30 38v10q0 48 35 74q18 14 43 8q27 -2 38 10t4 27.5t-26 23.5q0 6 -4 2q-16 6 -20 4 q-25 16 -22 44t18.5 54.5t14.5 53t-36 39.5q-285 -244 -358 -396q-24 -71 -25 -201q0 -34 2 -73zM449 702q-7 -3 -8 -7q0 -9 24 -23q16 -20 35 -15q8 31 -25 39v2q-2 2 -7 2t-7 2h-12zM590 909l2 -4h2q6 2 10 6q-8 0 -14 -2zM725 1546q38 3 74 3q136 0 223 -44 q8 -27 36.5 -35t57.5 -5t50.5 -8t19.5 -40l4 -4l2 -10q2 -2 2 4q50 -50 50 -69q0 -11 -18 -11h-5h-4q-35 4 -46 -30q-4 -14 -4 -29q0 -22 9 -46q14 -42 41 -55q25 -10 41 -4t13 25.5t-28 32.5q-2 23 12 22q27 -2 52 -43q10 -18 10 -31q0 -16 -14 -26q-4 -4 -7 -4l-2 -2 q-37 -27 -40 -114t-9 -103q-9 -19 -9 -32q0 -20 26 -22h4q36 0 57 35q10 18 4 41q-8 23 6.5 35t36 15t35.5 10.5t4 23.5q21 -11 35 -11q27 0 31 40q1 9 1 17q0 42 -26 69q10 43 33 16q20 -29 6 -59q2 -10 5 -16.5t10 -12.5t11.5 -8t15.5 -6t13 -6q2 0 4 -2q20 -8 21 -27 q0 -13 -10 -31q-24 -81 -116 -81q-27 0 -61 7q-31 -12 -51 -40.5t-38.5 -68.5t-30.5 -58q-44 -80 -44 -140q0 -58 42 -98q25 -23 51 -28t54 6.5t47.5 23.5t45.5 33q10 1 19 1q57 0 71 -59q5 -19 5 -35q0 -45 -31 -81q-4 -16 -4 -19q180 211 180 482l-6 86q-7 -4 -15 -5 q-23 0 -49 37q-20 29 -6 27q8 0 29 -16q20 4 30 12q-12 70 -36 135l-11.5 12t-9.5 11q0 4 -1 12t-1 14v2q-92 188 -273.5 304t-398.5 116q-41 0 -61 -2q-20 -4 -33 -6h-12q-66 -11 -119 -27zM752 1201q-3 -15 -4 -27q0 -24 14 -33q20 8 29.5 39.5t2 59.5t-29.5 26 q-2 0 -6 -1t-6 -1q10 -17 0 -63zM762 973q-10 -2 -10 -7q0 -7 20 -20h16q2 -2 -4 -8q25 18 11.5 28.5t-33.5 6.5zM862 1331l6 6q18 8 11 23q-2 4 -11 4q-2 2 -8 2q-2 0 -5 -1t-5 -1q-21 -4 -21 -21q0 -33 33 -12zM853 1315q-9 -13 -9 -27q0 -20 18 -41q18 -25 6 -53.5 t-11 -59t36 -40.5q10 25 42 64.5t48 74.5q10 21 10 44q0 14 -4 30q-11 46 -55 46q-7 0 -14 -1q-45 -6 -67 -37zM1196 1087q-8 0 -8 -4q0 -6 14 -20q-12 -31 15 -33q18 0 30 19q2 16 -17 28q-17 11 -30 11q-2 -1 -4 -1z" />
|
||||
<glyph unicode="˜" horiz-adv-x="512" d="M-656 510.5q-7 145.5 89 259.5l188 228q37 41 88 80q-45 88 -28.5 195.5t88.5 193.5l186 227q61 74 147.5 110.5t174.5 28.5q94 -8 170 -69l96 -82q109 -86 115 -240q4 -137 -91 -254l-188 -227q-39 -45 -88 -78q31 -68 31 -137q6 -139 -90 -254l-189 -228 q-59 -74 -144 -110.5t-175 -28.5q-98 10 -170 72l-97 80q-106 88 -113 233.5zM-460 528.5q-1 -63.5 42 -100.5l99 -80q25 -20 61 -24q86 -10 150 67l188 228q45 51 45 123q-4 49 -31 80h-10q-16 -33 -48 -48.5t-67 -7.5q-39 10 -60 45t-11 74l-4 2l-6 6l-41 33 q-37 -14 -72 -55l-189 -226q-45 -53 -46 -116.5zM-125 1234.5q-4 -58.5 31 -97.5h37q4 0 10 -2t8 -2l21 74q10 41 46 60t75 9q39 -8 59 -44t10 -77l-28 -114l10 -9q41 18 71 56l189 227q49 55 45 123q-4 57 -41 92l-98 80q-27 20 -62 27q-86 6 -149 -70l-189 -225 q-41 -49 -45 -107.5z" />
|
||||
<glyph unicode="–" horiz-adv-x="1024" d="M205 512v205h614v-205h-614z" />
|
||||
<glyph unicode="—" d="M205 512v205h1638v-205h-1638z" />
|
||||
<glyph unicode="‹" horiz-adv-x="1931" d="M160 707l919 708v-534l693 534v-1417l-693 534v-534z" />
|
||||
<glyph unicode="›" horiz-adv-x="1976" d="M182 -6v1417l692 -534v534l920 -709l-920 -708v534z" />
|
||||
<glyph unicode="™" horiz-adv-x="4038" d="M2213 293q0 123 110 209t267 86q160 0 160 -27v778v269q0 23 16.5 38t38.5 15h1030q23 0 39.5 -15.5t16.5 -37.5v-269v-1073h-2q-16 -94 -107.5 -181t-226.5 -87q-156 0 -265.5 87t-109.5 208q0 123 109.5 209t265.5 86q162 0 162 -27v692h-793v-960v-27h-2 q-14 -94 -106.5 -181t-225.5 -87q-156 0 -266.5 87t-110.5 208zM164 1642l196 197l494 -491l-197 -197l-82 82q2 -74 75 -204t210 -267q215 -215 461 -293l25 -8l-99 98l199 197l491 -492l-196 -198q-72 -72 -187 -73q-113 0 -267 70q-310 141 -646 480q-303 303 -436 620 q-77 184 -77 315q0 94 40 160z" />
|
||||
<glyph unicode="" horiz-adv-x="500" d="M0 0v0v0v0v0z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 35 KiB |