2
0

Compare commits

...

37 Commits
1.2.1 ... 1.3

Author SHA1 Message Date
Maarten Billemont
d429044f64 Remove debugging "Save" button + fix keybaord appearance.
[FIXED]     Keyboard didn't appear when app is reactivated.
[REMOVED]   "Save" option in user menu on lock screen was for debugging.
2012-08-05 11:40:50 +02:00
Maarten Billemont
b38e8d9ea6 Fix appearance of the Guide on start-up.
[FIXED]     When the unlock VC shows, the guide's appearance is aborted.
2012-08-05 11:18:58 +02:00
Maarten Billemont
c928b1ca2c Fix unlock screen when MOC unavailable.
[REMOVED]   Disable TestFlight for release, it doesn't work anyway.
[FIXED]     Properly handle unlock screen when there is no MOC yet.
[FIXED]     Some log statements format strings & arguments.
2012-08-04 23:15:50 +02:00
Maarten Billemont
553a14dced Empty SMS password in repo. 2012-08-04 13:46:43 +02:00
Maarten Billemont
8b8d727ee0 Fixes to key saving and searching.
[FIXED]     Saving of keyData in keychain.
[FIXED]     MPSearchDelegate before MOC is available.
[FIXED]     Tip during search didn't show up.
2012-08-04 12:22:36 +02:00
Maarten Billemont
4cdeab4256 Don't block the MOC lookup.
[IMPROVED]  Don't block when MOC is not yet ready, just return nil.
[IMPROVED]  Outdated tip links to info, icon links to site search.
[IMPROVED]  Minor improvements to error handling during import.
2012-08-04 10:16:58 +02:00
Maarten Billemont
bc3aa3255e Get rid of these binaries; they don't belong in a repo. 2012-07-30 10:19:46 +02:00
Maarten Billemont
f2fdca6a03 Add env var support to CLI.
[ADDED]     The Java CLI client can now take values for the username and
            master password from the environment.
2012-07-30 10:17:26 +02:00
Maarten Billemont
647235616e Fixes to import code.
[FIXED]     Don't recalculate key for each entry in import list.
[FIXED]     Use correct fetch request to find user entity for import.
[FIXED]     Properly schedule all use of MOC with performBlock*
[ADDED]     Use undoManager to revert failed import changes.
2012-07-30 07:58:18 +02:00
Maarten Billemont
b0b6dcc56b Make iCloud removal use file coordinators. 2012-07-29 15:36:00 +02:00
Maarten Billemont
918a240dba UI fixes to do with password type button and help.
[FIXED]     Password type button goes off-screen.
2012-07-29 13:43:11 +02:00
Maarten Billemont
830dcb45ff Word wall + fixes to: guide, export, UI.
[ADDED]     Word wall: Show some random dictionary words while user
            thinks of a new master password to give him some
            inspiration.
[IMPROVED]  Only select user after he choose to reset.
[IMPROVED]  Ugly backdrops in the guide images cleaned up.
[FIXED]     Export of sites should now include a password version field.
[FIXED]     Filename to save exported passwords into should use day of
            the month instead of day of the year.
[FIXED]     No search predicate was set when search scope button wasn't
            set.  As a result, sites from other users showed up.
2012-07-29 12:40:22 +02:00
Maarten Billemont
7be9884075 Better abstraction for key & algorithm + V1
[IMPROVED]  A master password key is now better abstracted in an object.
[IMPROVED]  A master password algorithm is now better astracted in an
            object.
[ADDED]     Elements now have a specific algorithm version.
[ADDED]     Automatic/explicit migration of elements.
[ADDED]     Searching outdated elements.
2012-07-17 22:57:11 +02:00
Maarten Billemont
5ca0d954bb UI improvements with regards to user name saving.
[IMPROVED]  Settings -> user button.
[IMPROVED]  More sensible and understandable logic.
[FIXED]     State handling of what to show when, etc.
2012-07-16 21:49:27 +02:00
Maarten Billemont
02d69261df Password settings alpha.
[FIXED]     Hide password settings when no element selected.
2012-07-16 21:03:15 +02:00
Maarten Billemont
fc60460935 Settings toggle and site user name.
[ADDED]     Allow saving a user name per site.  This is an optional
            addition, toggled by tapping the new settings icon.
            Obviously user names can't be recovered after loss.
2012-07-16 20:29:48 +02:00
Maarten Billemont
559e11b16e Video and send-to-phone.
[ADDED]     Site: Demo video.
[ADDED]     Site: Send-to-phone using email and SMS.
2012-07-16 18:45:11 +02:00
Maarten Billemont
0a72809b02 Add +1 to site & hide alert.
[FIXED]     Hide alert message when main view appears.
[ADDED]     Google +1 to site.
2012-07-15 15:23:40 +02:00
Maarten Billemont
8c71ed0081 Fix deleting of users.
[FIXED]     When retrieving content without a key set, return nil.
2012-07-15 00:09:20 +02:00
Maarten Billemont
3e19a026ba Accessibility improvements + Xcode 4.3 fixes.
[ADDED]     Accessibility hints and labels.
2012-07-14 23:57:06 +02:00
Maarten Billemont
8fa3c6c75d Improved algorithm page style.
[FIXED]     A few text fixes in algorithm page.
2012-07-14 23:55:22 +02:00
Maarten Billemont
217cf56d94 Versioning + automatic user selection + misc UI tweaks.
[ADDED]     Versioning and explicit migration to MPElementEntity.
[ADDED]     Upgrade button in case the element needs explicit migration.
[ADDED]     Messages in Crashlytics and TestFlight logs upon
            initialization so we can easily see it worked and what the
            client's versioning looks like.
[IMPROVED]  Only show firstRun UI tooltips once.
[IMPROVED]  Automatically select the latest user upon load of unlock.
[IMPROVED]  Automatically select the user when his password is reset.
[IMPROVED]  Hide active element when logging a user out.
2012-07-12 08:50:34 +02:00
Maarten Billemont
6f37f28a4c Element versioning + upgrade tool & tip.
[ADDED]     "version" to MPElementEntity.
[ADDED]     Tool tip, a tip that points at the content tool.
[ADDED]     Upgrade tool, a tool used for upgrading outdated elements.
2012-07-10 07:26:49 +02:00
Maarten Billemont
3b7d2dc08e Exclusion from GA.
[ADDED]     Site: Method of excluding oneself from Google Analytics.
2012-07-10 07:25:57 +02:00
Maarten Billemont
5e9af44736 Press Kit improvements and addition to site.
[IMPROVED]  Press Kit: Front page + grammar.
[ADDED]     Site: Press Kit download button.
[ADDED]     Site: GA /outbound/ events.
2012-07-09 10:47:00 +02:00
Maarten Billemont
be33a29fa0 AdWords + SEO
[ADDED]     Site: AdWords conversion detection.
[ADDED]     Site: SEO by schema.org tagging.
2012-07-07 01:20:37 +02:00
Maarten Billemont
29ed22d0b7 Badge + java version.
[ADDED]     Site: Price badge on front phone.
[ADDED]     Site: Mention Java version.
[ADDED]     Site: Sync script.
[IMPROVED]  Screenshots of lock screen.
2012-07-06 10:39:49 +02:00
Maarten Billemont
8b997528c9 Improved logging of password generation.
[IMPROVED]  Trace-level logging during password generation.
2012-07-05 13:47:50 +02:00
Maarten Billemont
04a6c8e68d A Java proof-of-concept CLI for Master Password. 2012-07-05 13:42:32 +02:00
Maarten Billemont
0e8e4dc06d Add site tip: Promo code for feedback. 2012-07-05 13:40:31 +02:00
Maarten Billemont
8b91c2a0b8 Press data and site experimentation with nivo-slider.
[ADDED]     Press resources: Releases, media, etc.
2012-07-05 00:02:45 +02:00
Maarten Billemont
e967affddb Added unit tests for playing around with the logic. 2012-07-04 23:57:29 +02:00
Maarten Billemont
dea7434bd4 prMac Press Release
[ADDED]     prMac press release for 1.2.1, REV1b
2012-07-03 11:02:58 +02:00
Maarten Billemont
1da63e450d High-resolution iTunesArtwork. 2012-07-03 11:00:25 +02:00
Maarten Billemont
029d240999 Fixed a crash when tapping a non-type row.
[FIXED]     MP-23 Crash when tapping non-type row
2012-07-03 10:04:39 +02:00
Maarten Billemont
110f7069e1 Lock when backgrounded.
[FIXED]     When backgrounding the app and re-showing it, don't reveal
            the UI when logout is enabled.
2012-06-28 00:02:00 +02:00
Maarten Billemont
d77cde1929 Moved MPSearchDelegate to code.
[FIXED]     Another potential crash because of an over-released
            top-level object in storyboard.
2012-06-27 23:44:37 +02:00
141 changed files with 34129 additions and 999 deletions

8
.gitignore vendored
View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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;

View File

@@ -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">

Binary file not shown.

View 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>

View File

@@ -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,
}

View File

@@ -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 );
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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];
}
}

View File

@@ -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" );
}
}

View File

@@ -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 );
}
}

View File

@@ -0,0 +1,10 @@
package com.lyndir.lhunath.masterpassword.entity;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPElementEntity {
}

View File

@@ -0,0 +1,10 @@
package com.lyndir.lhunath.masterpassword.entity;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPElementGeneratedEntity extends MPElementEntity {
}

View File

@@ -0,0 +1,10 @@
package com.lyndir.lhunath.masterpassword.entity;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPElementStoredEntity extends MPElementEntity {
}

View File

@@ -0,0 +1 @@
../../../../../../Resources/ciphers.plist

View 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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
cd "${BASH_SOURCE[0]%/*}"
java -jar masterpassword-cli-GIT-SNAPSHOT.jar "$@"

View 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>

View File

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

View File

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

View File

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

View File

@@ -1,54 +1,83 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPTypes.m
// 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

View File

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

View File

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

View File

@@ -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;

View File

@@ -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];

View File

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

View File

@@ -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;

View File

@@ -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: @""];
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.
//

View File

@@ -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.
//

View File

@@ -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.
//

View File

@@ -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.
//

View File

@@ -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;

View File

@@ -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
View File

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

66
MasterPassword/MPKey.m Normal file
View File

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

View File

@@ -6,9 +6,7 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#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"

View File

@@ -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

View File

@@ -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

View File

@@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MasterPassword.xcdatamodel</string>
<string>MasterPassword 2.xcdatamodel</string>
</dict>
</plist>

View File

@@ -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>

View File

@@ -18,6 +18,6 @@
- (void)showGuide;
- (void)export;
- (void)changeMasterPasswordFor:(MPUserEntity *)user;
- (void)changeMasterPasswordFor:(MPUserEntity *)user didResetBlock:(void(^)(void))didReset;
@end

View File

@@ -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];
}
}
}

View File

@@ -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;

View File

@@ -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]];

View File

@@ -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 {

View File

@@ -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

View File

@@ -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]];
}];
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

File diff suppressed because it is too large Load Diff

BIN
Resources/AppStore-iOS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

27413
Resources/dictionary.lst Normal file

File diff suppressed because it is too large Load Diff

View 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">&mdash; 2 &mdash;</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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

View File

@@ -0,0 +1 @@
../Press/./MasterPassword_PressKit.zip

View File

@@ -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 = &lt;empty&gt;
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>!@#$%^&amp;*()</code></li>
<li><code>@&amp;%?,=[]_:-+*$#!'^~;()/.</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!@#$%^&amp;*()</code></li>
<li><code>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789@&amp;%?,=[]_:-+*$#!'^~;()/.</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>, &copy; 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>, &copy; 2011.
</footer>
</body>

1090
Site/css/buttons/buttons.css Executable file

File diff suppressed because it is too large Load Diff

114
Site/css/buttons/demo.html Executable file
View 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>

Binary file not shown.

View 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="&#x09;" horiz-adv-x="512" />
<glyph unicode="&#xa0;" horiz-adv-x="512" />
<glyph unicode="!" />
<glyph unicode="&#x22;" />
<glyph unicode="#" />
<glyph unicode="$" />
<glyph unicode="%" />
<glyph unicode="&#x26;" />
<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="&#x3c;" horiz-adv-x="1931" d="M160 707l919 708v-534l693 534v-1417l-693 534v-534z" />
<glyph unicode="=" />
<glyph unicode="&#x3e;" 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="&#xad;" />
<glyph unicode="&#xb2;" horiz-adv-x="1734" d="M80 494v589h788v492l787 -787l-787 -786v492h-788z" />
<glyph unicode="&#xb3;" horiz-adv-x="1742" d="M84 782h491v787h590v-787h494l-787 -786z" />
<glyph unicode="&#xb9;" horiz-adv-x="1816" d="M121 786l788 787l787 -787h-492v-786h-590v786h-493z" />
<glyph unicode="&#xc0;" 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="&#xc2;" 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="&#xc3;" 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="&#xc8;" 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="&#xca;" 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="&#xcc;" 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="&#xce;" 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="&#xd1;" 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="&#xd2;" 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="&#xd4;" 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="&#xd5;" 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="&#xd9;" 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="&#xdb;" 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="&#xe3;" 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="&#xf1;" 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="&#xf5;" 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="&#x152;" 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="&#x2dc;" 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="&#x2013;" horiz-adv-x="1024" d="M205 512v205h614v-205h-614z" />
<glyph unicode="&#x2014;" d="M205 512v205h1638v-205h-1638z" />
<glyph unicode="&#x2039;" horiz-adv-x="1931" d="M160 707l919 708v-534l693 534v-1417l-693 534v-534z" />
<glyph unicode="&#x203a;" horiz-adv-x="1976" d="M182 -6v1417l692 -534v534l920 -709l-920 -708v534z" />
<glyph unicode="&#x2122;" 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="&#xe000;" horiz-adv-x="500" d="M0 0v0v0v0v0z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 35 KiB

Some files were not shown because too many files have changed in this diff Show More