2
0

Master password loading improvements + changing + docs.

[ADDED]     The master password can now be changed.
[IMPROVED]  Flow of handling the master password when activating the app.
[IMPROVED]  iTunesArtwork & icons.
[ADDED]     Elements are now scoped to the current master password.
[ADDED]     Lots of documentation for the user:
                - Settings
                - FAQ
                - A quickstart guide
This commit is contained in:
Maarten Billemont 2012-01-29 12:41:48 +01:00
parent e5ab2605f9
commit 571898632f
27 changed files with 460 additions and 168 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -6,6 +6,20 @@
objectVersion = 46; objectVersion = 46;
objects = { objects = {
/* Begin PBXAggregateTarget section */
DA6556DC14D55C1500841C99 /* InfoPlist */ = {
isa = PBXAggregateTarget;
buildConfigurationList = DA6556DD14D55C1600841C99 /* Build configuration list for PBXAggregateTarget "InfoPlist" */;
buildPhases = (
DA6556E014D55C2700841C99 /* ShellScript */,
);
dependencies = (
);
name = InfoPlist;
productName = InfoPlist;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
DA007F5214B24DCD00251337 /* OPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA007F5114B24DCD00251337 /* OPConfig.m */; }; DA007F5214B24DCD00251337 /* OPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA007F5114B24DCD00251337 /* OPConfig.m */; };
DA007F5514B25EE100251337 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA007F5414B25EE100251337 /* ciphers.plist */; }; DA007F5514B25EE100251337 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA007F5414B25EE100251337 /* ciphers.plist */; };
@ -656,6 +670,13 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
DA6556E114D55C4400841C99 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
proxyType = 1;
remoteGlobalIDString = DA6556DC14D55C1500841C99;
remoteInfo = InfoPlist;
};
DAC63281148681190075AEA5 /* PBXContainerItemProxy */ = { DAC63281148681190075AEA5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */; containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
@ -1738,8 +1759,6 @@
DA5BFA39147E415C00F98B1E = { DA5BFA39147E415C00F98B1E = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */,
DAC632871486D95D0075AEA5 /* Security.framework */,
DA5BFA50147E415C00F98B1E /* OnePassword */, DA5BFA50147E415C00F98B1E /* OnePassword */,
DAC77CAF148291A600BCF976 /* Pearl */, DAC77CAF148291A600BCF976 /* Pearl */,
DAC6325F1486805C0075AEA5 /* uicolor-utilities */, DAC6325F1486805C0075AEA5 /* uicolor-utilities */,
@ -1763,6 +1782,8 @@
DA5BFA47147E415C00F98B1E /* Frameworks */ = { DA5BFA47147E415C00F98B1E /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */,
DAC632871486D95D0075AEA5 /* Security.framework */,
DA5BFA48147E415C00F98B1E /* UIKit.framework */, DA5BFA48147E415C00F98B1E /* UIKit.framework */,
DA5BFA4A147E415C00F98B1E /* Foundation.framework */, DA5BFA4A147E415C00F98B1E /* Foundation.framework */,
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */, DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */,
@ -2271,6 +2292,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "OnePassword" */; buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "OnePassword" */;
buildPhases = ( buildPhases = (
DA6556E314D55F3000841C99 /* ShellScript */,
DA5BFA40147E415C00F98B1E /* Sources */, DA5BFA40147E415C00F98B1E /* Sources */,
DA5BFA41147E415C00F98B1E /* Frameworks */, DA5BFA41147E415C00F98B1E /* Frameworks */,
DA5BFA42147E415C00F98B1E /* Resources */, DA5BFA42147E415C00F98B1E /* Resources */,
@ -2278,6 +2300,7 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
DA6556E214D55C4400841C99 /* PBXTargetDependency */,
DAC63282148681190075AEA5 /* PBXTargetDependency */, DAC63282148681190075AEA5 /* PBXTargetDependency */,
); );
name = OnePassword; name = OnePassword;
@ -2365,6 +2388,7 @@
DAC77CAC148291A600BCF976 /* Pearl */, DAC77CAC148291A600BCF976 /* Pearl */,
DAC6325C1486805C0075AEA5 /* uicolor-utilities */, DAC6325C1486805C0075AEA5 /* uicolor-utilities */,
DAC6326B148680650075AEA5 /* jrswizzle */, DAC6326B148680650075AEA5 /* jrswizzle */,
DA6556DC14D55C1500841C99 /* InfoPlist */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -2926,6 +2950,37 @@
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
DA6556E014D55C2700841C99 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
InfoPlist.h,
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = "/usr/bin/env bash";
shellScript = "cat > \"$SCRIPT_OUTPUT_FILE_0\" <<. \n#define GIT_COMMIT $(git describe --tags --always --dirty --long)\n#define GIT_TAG $(git describe --tags | sed 's/-[^-]*-[^-]*$//')h\n#define GIT_COMMIT_YEAR $(git log --format=format:%ci HEAD^.. | sed 's/-.*//')\n.\ntouch /Users/lhunath/Documents/workspace/lyndir/OnePassword/OnePassword/OnePassword-Info.plist";
};
DA6556E314D55F3000841C99 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "PATH+=:/usr/libexec\n\nPlistBuddy -c \"Set :CFBundleVersion $(git describe --tags --always --dirty --long)\" \"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nPlistBuddy -c \"Set :CFBundleShortVersionString $(git describe --tags | sed 's/-[^-]*-[^-]*$//')\" \"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\n\nPlistBuddy -c \"Set :CFBundleShortVersionString $(git describe --tags | sed 's/-[^-]*-[^-]*$//')\" \"OnePassword/Settings.bundle/Root.plist\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
DA5BFA40147E415C00F98B1E /* Sources */ = { DA5BFA40147E415C00F98B1E /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
@ -3006,6 +3061,11 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
DA6556E214D55C4400841C99 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DA6556DC14D55C1500841C99 /* InfoPlist */;
targetProxy = DA6556E114D55C4400841C99 /* PBXContainerItemProxy */;
};
DAC63282148681190075AEA5 /* PBXTargetDependency */ = { DAC63282148681190075AEA5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = DAC77CAC148291A600BCF976 /* Pearl */; target = DAC77CAC148291A600BCF976 /* Pearl */;
@ -3114,6 +3174,20 @@
}; };
name = Release; name = Release;
}; };
DA6556DE14D55C1600841C99 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
DA6556DF14D55C1600841C99 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
DAC632661486805C0075AEA5 /* Debug */ = { DAC632661486805C0075AEA5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@ -3209,6 +3283,14 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
DA6556DD14D55C1600841C99 /* Build configuration list for PBXAggregateTarget "InfoPlist" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA6556DE14D55C1600841C99 /* Debug */,
DA6556DF14D55C1600841C99 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = { DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@ -306,7 +306,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<navigationItem key="navigationItem" title="Type" id="rak-Td-wu1"/> <navigationItem key="navigationItem" title="Type" id="rak-Td-wu1"/>
</tableViewController> </tableViewController>
</objects> </objects>
<point key="canvasLocation" x="962" y="182"/> <point key="canvasLocation" x="996" y="182"/>
</scene> </scene>
<scene sceneID="U26-Zf-euQ"> <scene sceneID="U26-Zf-euQ">
<objects> <objects>
@ -479,21 +479,24 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<rect key="frame" x="0.0" y="0.0" width="320" height="58"/> <rect key="frame" x="0.0" y="0.0" width="320" height="58"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<webView opaque="NO" contentMode="scaleToFill" id="8FQ-x4-lR9" customClass="DebuggableUIWebView"> <webView opaque="NO" contentMode="scaleToFill" id="8FQ-x4-lR9">
<rect key="frame" x="0.0" y="1" width="320" height="199"/> <rect key="frame" x="0.0" y="1" width="320" height="199"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<connections>
<outlet property="delegate" destination="PQa-Xl-A3x" id="H1z-xP-1Pm"/>
</connections>
</webView> </webView>
</subviews> </subviews>
<color key="backgroundColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/> <color key="backgroundColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
</view> </view>
<view alpha="0.0" contentMode="scaleToFill" id="yRY-qt-gz8"> <view alpha="0.0" contentMode="scaleToFill" id="yRY-qt-gz8">
<rect key="frame" x="10" y="250" width="300" height="150"/> <rect key="frame" x="10" y="230" width="300" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_alert_black.png" id="TUv-Tl-xTc"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_alert_black.png" id="TUv-Tl-xTc">
<rect key="frame" x="0.0" y="0.0" width="300" height="150"/> <rect key="frame" x="0.0" y="0.0" width="300" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/> <rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Password Changed!" lineBreakMode="tailTruncation" minimumFontSize="10" id="ZdH-we-KcW"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Password Changed!" lineBreakMode="tailTruncation" minimumFontSize="10" id="ZdH-we-KcW">
@ -503,8 +506,8 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="jCg-t7-RuK"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" id="jCg-t7-RuK">
<rect key="frame" x="20" y="60" width="260" height="70"/> <rect key="frame" x="20" y="60" width="260" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<string key="text">The password for apple.com has changed. <string key="text">The password for apple.com has changed.
@ -567,7 +570,7 @@ L4m3P4sSw0rD</string>
</subviews> </subviews>
<color key="backgroundColor" red="0.14901960784313725" green="0.16470588235294117" blue="0.1803921568627451" alpha="1" colorSpace="calibratedRGB"/> <color key="backgroundColor" red="0.14901960784313725" green="0.16470588235294117" blue="0.1803921568627451" alpha="1" colorSpace="calibratedRGB"/>
</view> </view>
<navigationItem key="navigationItem" title="One Password" id="hB9-S6-KHC"> <navigationItem key="navigationItem" title="Master Password" id="hB9-S6-KHC">
<barButtonItem key="rightBarButtonItem" id="Csu-uI-sPX"> <barButtonItem key="rightBarButtonItem" id="Csu-uI-sPX">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="iwA-Sh-vZO"> <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="iwA-Sh-vZO">
<rect key="frame" x="283" y="6" width="32" height="32"/> <rect key="frame" x="283" y="6" width="32" height="32"/>
@ -610,6 +613,30 @@ L4m3P4sSw0rD</string>
</objects> </objects>
<point key="canvasLocation" x="455" y="182"/> <point key="canvasLocation" x="455" y="182"/>
</scene> </scene>
<scene sceneID="Rt1-b4-sUB">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController id="qz3-eG-aEi" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="cwa-ct-2DY">
<rect key="frame" x="0.0" y="20" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="bottom" image="app.png" id="vz0-cw-aVL">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<view alpha="0.80000000000000004" contentMode="scaleToFill" id="XR2-Mq-TMD">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="viewFlipsideBackgroundColor"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
</objects>
<point key="canvasLocation" x="455" y="793"/>
</scene>
<scene sceneID="8r0-wA-Zre"> <scene sceneID="8r0-wA-Zre">
<objects> <objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -632,6 +659,7 @@ L4m3P4sSw0rD</string>
<resources> <resources>
<image name="Content-Backdrop.png" width="480" height="480"/> <image name="Content-Backdrop.png" width="480" height="480"/>
<image name="Square-bottom.png" width="551" height="58"/> <image name="Square-bottom.png" width="551" height="58"/>
<image name="app.png" width="320" height="480"/>
<image name="icon_cancel.png" width="32" height="32"/> <image name="icon_cancel.png" width="32" height="32"/>
<image name="icon_edit.png" width="32" height="32"/> <image name="icon_edit.png" width="32" height="32"/>
<image name="icon_plus.png" width="32" height="32"/> <image name="icon_plus.png" width="32" height="32"/>
@ -645,46 +673,6 @@ L4m3P4sSw0rD</string>
<image name="ui_panel_container.png" width="300" height="87"/> <image name="ui_panel_container.png" width="300" height="87"/>
<image name="ui_panel_display.png" width="300" height="86"/> <image name="ui_panel_display.png" width="300" height="86"/>
</resources> </resources>
<classes>
<class className="OPMainViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/OPMainViewController.h"/>
<relationships>
<relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="toggleHelp"/>
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertTitle" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentField" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentTipEditIcon" candidateClass="UIImageView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="searchResultsController" candidateClass="OPSearchDelegate"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
</relationships>
</class>
<class className="OPSearchDelegate" superclassName="NSObject">
<source key="sourceIdentifier" type="project" relativePath="./Classes/OPSearchDelegate.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="OPTypeViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/OPTypeViewController.h"/>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics"> <simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/> <simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/>
<simulatedOrientationMetrics key="orientation"/> <simulatedOrientationMetrics key="orientation"/>

View File

@ -14,10 +14,12 @@
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (strong, nonatomic) NSString *keyPhrase; @property (strong, nonatomic) NSString *keyPhrase;
@property (strong, nonatomic) NSData *keyPhraseHash;
@property (strong, nonatomic) NSString *keyPhraseHashHex;
+ (OPAppDelegate *)get; + (OPAppDelegate *)get;
+ (NSManagedObjectContext *)managedObjectContext;
+ (NSManagedObjectModel *)managedObjectModel; + (NSManagedObjectModel *)managedObjectModel;
+ (NSManagedObjectContext *)managedObjectContext;
- (void)saveContext; - (void)saveContext;
- (NSURL *)applicationDocumentsDirectory; - (NSURL *)applicationDocumentsDirectory;

View File

@ -15,19 +15,27 @@
+ (NSDictionary *)keyPhraseQuery; + (NSDictionary *)keyPhraseQuery;
+ (NSDictionary *)keyPhraseHashQuery; + (NSDictionary *)keyPhraseHashQuery;
- (void)loadKeyPhrase;
- (void)forgetKeyPhrase;
- (void)loadStoredKeyPhrase;
- (void)askKeyPhrase;
@end @end
@implementation OPAppDelegate @implementation OPAppDelegate
@synthesize managedObjectContext = __managedObjectContext;
@synthesize managedObjectModel = __managedObjectModel; @synthesize managedObjectModel = __managedObjectModel;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator; @synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
@synthesize keyPhrase = _keyPhrase; @synthesize keyPhrase = _keyPhrase;
@synthesize keyPhraseHash = _keyPhraseHash;
@synthesize keyPhraseHashHex = _keyPhraseHashHex;
+ (void)initialize { + (void)initialize {
#ifdef DEBUG #ifdef DEBUG
[Logger get].autoprintLevel = LogLevelDebug; [Logger get].autoprintLevel = LogLevelTrace;
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)]; [NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
#endif #endif
} }
@ -37,7 +45,7 @@
static NSDictionary *OPKeyPhraseQuery = nil; static NSDictionary *OPKeyPhraseQuery = nil;
if (!OPKeyPhraseQuery) if (!OPKeyPhraseQuery)
OPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword OPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhrase" attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
forKey:(__bridge id)kSecAttrService] forKey:(__bridge id)kSecAttrService]
matches:nil]; matches:nil];
@ -49,7 +57,7 @@
static NSDictionary *OPKeyPhraseHashQuery = nil; static NSDictionary *OPKeyPhraseHashQuery = nil;
if (!OPKeyPhraseHashQuery) if (!OPKeyPhraseHashQuery)
OPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword OPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhraseHash" attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
forKey:(__bridge id)kSecAttrService] forKey:(__bridge id)kSecAttrService]
matches:nil]; matches:nil];
@ -114,6 +122,52 @@
} }
- (void)applicationDidBecomeActive:(UIApplication *)application { - (void)applicationDidBecomeActive:(UIApplication *)application {
[self loadKeyPhrase];
}
- (void)loadKeyPhrase {
if ([[OPConfig get].forgetKeyPhrase boolValue]) {
[self forgetKeyPhrase];
return;
}
[self loadStoredKeyPhrase];
if (!self.keyPhrase) {
// Key phrase is not known. Ask user to set/specify it.
dbg(@"Key phrase not known. Will ask user.");
[self askKeyPhrase];
return;
}
}
- (void)forgetKeyPhrase {
dbg(@"Forgetting key phrase.");
[AlertViewController showAlertWithTitle:@"Changing Master Password"
message:
@"You've requested to change your master password.\n\n"
@"If you continue, your current sites and passwords will become unavailable.\n\n"
@"You can always change back to the old master password later.\n"
@"Your old sites and passwords will then become available again."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert firstOtherButtonIndex]) {
// Key phrase reset. Delete it.
dbg(@"Deleting master key phrase and hash from key chain.");
[KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]];
[KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseHashQuery]];
}
[self loadKeyPhrase];
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
[OPConfig get].forgetKeyPhrase = [NSNumber numberWithBool:NO];
}
- (void)loadStoredKeyPhrase {
if ([[OPConfig get].storeKeyPhrase boolValue]) { if ([[OPConfig get].storeKeyPhrase boolValue]) {
// Key phrase is stored in keychain. Load it. // Key phrase is stored in keychain. Load it.
dbg(@"Loading master key phrase from key chain."); dbg(@"Loading master key phrase from key chain.");
@ -127,28 +181,30 @@
dbg(@"Deleting master key phrase from key chain."); dbg(@"Deleting master key phrase from key chain.");
[KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]]; [KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]];
} }
}
if (!self.keyPhrase) { - (void)askKeyPhrase {
// Key phrase is not known. Ask user to set/specify it.
dbg(@"Key phrase not known. Will ask user.");
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseHashQuery]]; NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseHashQuery]];
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known"); dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
AlertViewController *keyPhraseAlert = [[AlertViewController alloc] initQuestionWithTitle:@"One Password" [AlertViewController showAlertWithTitle:@"One Password"
message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:" message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:"
viewStyle:UIAlertViewStyleSecureTextInput
tappedButtonBlock: tappedButtonBlock:
^(NSInteger buttonIndex, NSString *answer) { ^(UIAlertView *alert, NSInteger buttonIndex) {
if (!buttonIndex) if (buttonIndex == [alert cancelButtonIndex])
exit(0); exit(0);
NSString *answer = [alert textFieldAtIndex:0].text;
if (![answer length]) { if (![answer length]) {
// User didn't enter a key phrase. // User didn't enter a key phrase.
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError [AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
message:@"No master password entered." message:@"No master password entered."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock: tappedButtonBlock:
^(NSInteger buttonIndex) { ^(UIAlertView *alert, NSInteger buttonIndex) {
exit(0); exit(0);
} cancelTitle:@"Quit" otherTitles:nil]; } cancelTitle:@"Quit" otherTitles:nil];
} }
@ -162,8 +218,9 @@
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError [AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
message:@"Incorrect master password." message:@"Incorrect master password."
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock: tappedButtonBlock:
^(NSInteger buttonIndex) { ^(UIAlertView *alert, NSInteger buttonIndex) {
exit(0); exit(0);
} cancelTitle:@"Quit" otherTitles:nil]; } cancelTitle:@"Quit" otherTitles:nil];
@ -172,15 +229,8 @@
self.keyPhrase = answer; self.keyPhrase = answer;
} cancelTitle:@"Quit" otherTitles:@"Unlock", nil]; } cancelTitle:@"Quit" otherTitles:@"Unlock", nil];
keyPhraseAlert.alertField.autocapitalizationType = UITextAutocapitalizationTypeNone;
keyPhraseAlert.alertField.autocorrectionType = UITextAutocorrectionTypeNo;
keyPhraseAlert.alertField.enablesReturnKeyAutomatically = YES;
keyPhraseAlert.alertField.returnKeyType = UIReturnKeyGo;
keyPhraseAlert.alertField.secureTextEntry = YES;
[keyPhraseAlert showAlert];
}); });
} }
}
- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
@ -228,10 +278,12 @@
_keyPhrase = keyPhrase; _keyPhrase = keyPhrase;
if (keyPhrase) { if (keyPhrase) {
NSData *keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512]; self.keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
dbg(@"Updating master key phrase hash to: %@.", keyPhraseHash); self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
[KeyChain addOrUpdateItemForQuery:[OPAppDelegate keyPhraseHashQuery] [KeyChain addOrUpdateItemForQuery:[OPAppDelegate keyPhraseHashQuery]
withAttributes:[NSDictionary dictionaryWithObject:keyPhraseHash withAttributes:[NSDictionary dictionaryWithObject:self.keyPhraseHash
forKey:(__bridge id)kSecValueData]]; forKey:(__bridge id)kSecValueData]];
if ([[OPConfig get].storeKeyPhrase boolValue]) { if ([[OPConfig get].storeKeyPhrase boolValue]) {
dbg(@"Storing master key phrase in key chain."); dbg(@"Storing master key phrase in key chain.");

View File

@ -11,6 +11,7 @@
@property (nonatomic, retain) NSNumber *dataStoreError; @property (nonatomic, retain) NSNumber *dataStoreError;
@property (nonatomic, retain) NSNumber *storeKeyPhrase; @property (nonatomic, retain) NSNumber *storeKeyPhrase;
@property (nonatomic, retain) NSNumber *rememberKeyPhrase; @property (nonatomic, retain) NSNumber *rememberKeyPhrase;
@property (nonatomic, retain) NSNumber *forgetKeyPhrase;
@property (nonatomic, retain) NSNumber *helpHidden; @property (nonatomic, retain) NSNumber *helpHidden;
+ (OPConfig *)get; + (OPConfig *)get;

View File

@ -10,7 +10,7 @@
@implementation OPConfig @implementation OPConfig
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden; @dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, forgetKeyPhrase, helpHidden;
- (id)init { - (id)init {
@ -22,6 +22,7 @@
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberKeyPhrase)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(forgetKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
nil]]; nil]];

View File

@ -13,14 +13,12 @@
@interface OPElementEntity : NSManagedObject @interface OPElementEntity : NSManagedObject
@property (nonatomic, retain) NSString *name; @property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *mpHashHex;
@property (nonatomic) int16_t type; @property (nonatomic) int16_t type;
@property (nonatomic) int16_t uses; @property (nonatomic) int16_t uses;
@property (nonatomic) NSTimeInterval lastUsed; @property (nonatomic) NSTimeInterval lastUsed;
@property (nonatomic, retain) NSString *contentUTI;
@property (nonatomic) int16_t contentType;
- (void)use; - (void)use;
- (id)content; - (id)content;
- (NSString *)contentDescription;
@end @end

View File

@ -12,11 +12,10 @@
@implementation OPElementEntity @implementation OPElementEntity
@dynamic name; @dynamic name;
@dynamic mpHashHex;
@dynamic type; @dynamic type;
@dynamic uses; @dynamic uses;
@dynamic lastUsed; @dynamic lastUsed;
@dynamic contentUTI;
@dynamic contentType;
- (void)use { - (void)use {
@ -29,9 +28,15 @@
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil]; @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
} }
- (NSString *)contentDescription { - (NSString *)description {
return [[self content] description]; return [[self content] description];
} }
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed];
}
@end @end

View File

@ -16,6 +16,8 @@
- (id)content { - (id)content {
assert(self.type & OPElementTypeClassCalculated);
if (![self.name length]) if (![self.name length])
return nil; return nil;

View File

@ -10,7 +10,7 @@
#import "OPElementEntity.h" #import "OPElementEntity.h"
#import "OPSearchDelegate.h" #import "OPSearchDelegate.h"
@interface OPMainViewController : UIViewController <OPTypeDelegate, UITextFieldDelegate, UISearchBarDelegate, OPSearchResultsDelegate> @interface OPMainViewController : UIViewController <OPTypeDelegate, UITextFieldDelegate, UISearchBarDelegate, OPSearchResultsDelegate, UIWebViewDelegate>
@property (strong, nonatomic) OPElementEntity *activeElement; @property (strong, nonatomic) OPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet OPSearchDelegate *searchResultsController; @property (strong, nonatomic) IBOutlet OPSearchDelegate *searchResultsController;

View File

@ -84,7 +84,7 @@
// Because IB's edit button doesn't auto-toggle self.editable like editButtonItem does. // Because IB's edit button doesn't auto-toggle self.editable like editButtonItem does.
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]]; self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
if (![OPAppDelegate get].keyPhrase) { if (![OPAppDelegate get].keyPhrase) {
self.activeElement = nil; self.activeElement = nil;
@ -171,9 +171,10 @@
self.passwordCounter.text = [NSString stringWithFormat:@"%d", ((OPElementGeneratedEntity *) self.activeElement).counter]; self.passwordCounter.text = [NSString stringWithFormat:@"%d", ((OPElementGeneratedEntity *) self.activeElement).counter];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *contentDescription = self.activeElement.contentDescription; NSString *description = self.activeElement.description;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
self.contentField.text = contentDescription; self.contentField.text = description;
}); });
}); });
} }
@ -223,7 +224,7 @@
return; return;
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content [[UIPasteboard generalPasteboard] setValue:self.activeElement.content
forPasteboardType:self.activeElement.contentUTI]; forPasteboardType:(id)kUTTypeUTF8PlainText];
[self showContentTip:@"Copied!" withIcon:nil]; [self showContentTip:@"Copied!" withIcon:nil];
} }
@ -242,9 +243,9 @@
- (void)updateElement:(void (^)(void))updateElement { - (void)updateElement:(void (^)(void))updateElement {
// Update password counter. // Update password counter.
NSString *oldPassword = self.activeElement.contentDescription; NSString *oldPassword = self.activeElement.description;
updateElement(); updateElement();
NSString *newPassword = self.activeElement.contentDescription; NSString *newPassword = self.activeElement.description;
[self updateAnimated:YES]; [self updateAnimated:YES];
// Show new and old password. // Show new and old password.
@ -304,10 +305,9 @@
OPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromOPElementType(type) OPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromOPElementType(type)
inManagedObjectContext:[OPAppDelegate managedObjectContext]]; inManagedObjectContext:[OPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name; newElement.name = self.activeElement.name;
newElement.mpHashHex = self.activeElement.mpHashHex;
newElement.uses = self.activeElement.uses; newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed; newElement.lastUsed = self.activeElement.lastUsed;
newElement.contentUTI = self.activeElement.contentUTI;
newElement.contentType = self.activeElement.contentType;
[[OPAppDelegate managedObjectContext] deleteObject:self.activeElement]; [[OPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement; self.activeElement = newElement;
@ -315,7 +315,7 @@
self.activeElement.type = type; self.activeElement.type = type;
if (type & OPElementTypeClassStored && !self.activeElement.contentDescription) if (type & OPElementTypeClassStored && ![self.activeElement.description length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon]; [self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}]; }];
} }
@ -362,4 +362,14 @@
} }
} }
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
return YES;
}
@end @end

View File

@ -61,15 +61,18 @@
- (void)update { - (void)update {
NSString *text = self.searchDisplayController.searchBar.text; NSString *query = self.searchDisplayController.searchBar.text;
if (!text) if (!query)
text = @""; query = @"";
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([OPElementEntity class])]; NSFetchRequest *fetchRequest = [[OPAppDelegate get].managedObjectModel
fetchRequestFromTemplateWithName:@"OPSearchElement"
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
query, @"query",
[OPAppDelegate get].keyPhraseHashHex, @"mpHashHex",
nil]];
[fetchRequest setSortDescriptors: [fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]]; [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
[fetchRequest setPredicate:
[NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", text]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[OPAppDelegate managedObjectContext] managedObjectContext:[OPAppDelegate managedObjectContext]
@ -196,7 +199,10 @@
// "New" section. // "New" section.
element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([OPElementGeneratedEntity class]) element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([OPElementGeneratedEntity class])
inManagedObjectContext:[OPAppDelegate managedObjectContext]]; inManagedObjectContext:[OPAppDelegate managedObjectContext]];
assert([element isKindOfClass:ClassFromOPElementType(element.type)]);
element.name = self.searchDisplayController.searchBar.text; element.name = self.searchDisplayController.searchBar.text;
element.mpHashHex = [OPAppDelegate get].keyPhraseHashHex;
} }
[self.delegate didSelectElement:element]; [self.delegate didSelectElement:element];

View File

@ -37,13 +37,15 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>[auto]</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>[auto]</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©2011-2012, Lyndir</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>MainStoryboard_iPhone</string> <string>MainStoryboard_iPhone</string>
<key>UIMainStoryboardFile~ipad</key> <key>UIMainStoryboardFile~ipad</key>

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="872" systemVersion="11C74" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic"> <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="872" systemVersion="11C74" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="OPElementEntity" representedClassName="OPElementEntity" isAbstract="YES" syncable="YES"> <entity name="OPElementEntity" representedClassName="OPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="contentType" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="contentUTI" attributeType="String" defaultValueString="public.plain-text" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/> <attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="mpHashHex" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" indexed="YES" syncable="YES"/> <attribute name="name" attributeType="String" indexed="YES" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="257" syncable="YES"/> <attribute name="type" attributeType="Integer 16" defaultValueString="257" syncable="YES"/>
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/> <attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
@ -14,7 +13,7 @@
<entity name="OPElementStoredEntity" representedClassName="OPElementStoredEntity" parentEntity="OPElementEntity" syncable="YES"> <entity name="OPElementStoredEntity" representedClassName="OPElementStoredEntity" parentEntity="OPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/> <attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity> </entity>
<fetchRequest name="OPSearchElement" entity="OPElementEntity" predicateString="name BEGINSWITH &quot;$query&quot;"/> <fetchRequest name="OPSearchElement" entity="OPElementEntity" predicateString="name BEGINSWITH[cd] $query AND mpHashHex == $mpHashHex"/>
<elements> <elements>
<element name="OPElementEntity" positionX="160" positionY="192" width="128" height="135"/> <element name="OPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
<element name="OPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/> <element name="OPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -9,7 +9,7 @@
font: 16px "Baskerville"; font: 16px "Baskerville";
} }
h1, h2 { h1, h2 {
margin-top: 1em; margin-top: 1.5em;
padding-top: 1em; padding-top: 1em;
font-family: inherit; font-family: inherit;
font-weight: bold; font-weight: bold;
@ -20,12 +20,19 @@
i { i {
font-weight: bold; font-weight: bold;
} }
q {
font-style: italic;
}
img { img {
display: inline-block; display: inline-block;
height: 1.4em; height: 1.4em;
margin: -0.2em 0; margin: -0.2em 0;
vertical-align: middle; vertical-align: middle;
} }
a, a:link {
color: inherit;
font-weight: bold;
}
</style> </style>
<script src="jquery-1.6.1.min.js" type="text/javascript"></script> <script src="jquery-1.6.1.min.js" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
@ -70,5 +77,100 @@
This is useful, for example, when you've had to share the password with somebody else. This is useful, for example, when you've had to share the password with somebody else.
</p> </p>
<h2 id="faq">&mdash; F.A.Q. &mdash;</h2>
<h3>What is this thing?<br />
How do I use it?</h3>
<p>
The idea of this application is that you <b>change all of your passwords</b> everywhere to a password
generated by this app.
</p>
<h3>That's crazy talk.<br />
Why would I do that?</h3>
<p>
Everybody everywhere uses passwords for authentication nowadays. The theory of password authentication is
simple: It's a secret word that <b>only you</b> and the other party know. So, because nobody else knows
your secret password, nobody else can log into your <abbr>E-Mail</abbr> or Twitter account with it.
</p>
<p>
That's fine and dandy in theory. In practice, it's an <b>absolute hell</b>. In modern times, people have
hundreds of accounts on sites all over the Internet. So does that mean we're all remembering hundreds of
secret passwords? No, of course not. That would be <i>impossible</i>. If you're like most people, you
remember one or two passwords, and use those for all your sites everywhere.
</p>
<p>
<q>So what</q>, you say? Here's the problem: You're not the only one that knows your secret password. Each
time your make an account with a site and tell them your secret password, <i>they know it too</i>! Nothing
is stopping them from trying to log into GMail, Hotmail or Twitter with the same password and username you
used to register with their own site. Even if you only give your password to sites you trust, all it takes
is for one of those sites to get hacked and loose their password database. Those hackers now have all it
takes to impersonate you. This is, in fact, so common, that it's one of the main reasons people's accounts
are getting hacked or compromised nowadays.
</p>
<p>
This app <b>solves the problem</b> by letting you remember only a single password without requiring you to
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>Uh, so what if I loose my device?<br />
I'm locked out of everything?</h3>
<p>
<b>Absolutely not!</b> In fact, generated passwords aren't even stored on your device. No, not in the
cloud either. They're not stored anywhere! What that basically means is, if you grab the iPhone of the
person sitting next to you and open this app on it with your own master password, <i>it'll give you all
your generated passwords</i>. So, if you loose your phone or forget it, just borrow a friend's phone or
get a new one, and you're back in business. No backups or restores needed.
</p>
<p>
That also means that, unlike all those apps that store your passwords or send them off to be stored
on the internet, your passwords are much safer. Nobody knows about them. If your device is stolen,
the thieves can't get at them.
</p>
<h3>I entered my master password on my friend's app. It just says <q>Invalid master password</q> and exits!</h3>
<p>
For your own protection and to avoid opening the app after <i>mistyping</i> your master password (and thus
generating bad passwords for your sites), the app normally checks whether you're entering the same master
password as the one you've been using before.
</p>
<p>
To open the app with <b>a different master password</b>, just go into your device's <code>Settings</code>, find
<code>Master Password</code>'s settings somewhere near the bottom, and flip the setting: <code>Change my
password</code>. The next time you open the app, you can enter a new master password. Don't worry:
you're not wiping your friend's passwords this way. All he needs to do to get back at his own passwords is
flip the switch again and change back to his own master password.
</p>
<h3>Great, but that still means I need my phone to access anything.</h3>
<p>
Correct. However, remember that usually you'll only need to use this app once for each site. Imagine
you're usually on your MacBook Air. You go to Twitter, it asks for your password. You get your phone out,
start the app and generate the password for <code>twitter.com</code>. You copy the password manually by
typing it out on your MacBook Air. You may notice that the generated passwords have been created such that
they're fairly easy to copy. Once you log in, though, your MacBook will ask you to save the password in its
key chain. Agree to that, and you won't need to bring up your phone again the next time you log in to
Twitter.
</p>
<p>
If you're still worried, the app's homepage details <b>the algorithm</b> used to generate your passwords.
There's also <b>a Bash script</b> that you can use to generate your passwords on any POSIX system (such as,
your Mac). A Mac version of this app is also in the works so that you can easily get to all your passwords
without needing to bring up your phone.
</p>
<h3>This stuff is gold.<br />
I want one branded for our company.</h3>
<p>
Contact me directly for enterprise inquiries. I can provide branded clients and enterprise distribution
if your company is interested in deploying this solution internally.
</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>
</footer>
</body> </body>
</html> </html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 152 KiB

View File

@ -4,6 +4,14 @@
<dict> <dict>
<key>PreferenceSpecifiers</key> <key>PreferenceSpecifiers</key>
<array> <array>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string></string>
<key>FooterText</key>
<string>http://masterpassword.lyndir.com</string>
</dict>
<dict> <dict>
<key>Type</key> <key>Type</key>
<string>PSTitleValueSpecifier</string> <string>PSTitleValueSpecifier</string>
@ -11,6 +19,8 @@
<string>Version</string> <string>Version</string>
<key>Key</key> <key>Key</key>
<string>version</string> <string>version</string>
<key>DefaultValue</key>
<string></string>
</dict> </dict>
<dict> <dict>
<key>Type</key> <key>Type</key>
@ -19,6 +29,8 @@
<string>Build</string> <string>Build</string>
<key>Key</key> <key>Key</key>
<string>build</string> <string>build</string>
<key>DefaultValue</key>
<string></string>
</dict> </dict>
<dict> <dict>
<key>Type</key> <key>Type</key>
@ -27,12 +39,34 @@
<string>Copyright</string> <string>Copyright</string>
<key>Key</key> <key>Key</key>
<string>copyright</string> <string>copyright</string>
<key>DefaultValue</key>
<string></string>
</dict> </dict>
<dict> <dict>
<key>Type</key> <key>Type</key>
<string>PSGroupSpecifier</string> <string>PSGroupSpecifier</string>
<key>Title</key> <key>Title</key>
<string>Security</string> <string>Master Password</string>
<key>FooterText</key>
<string>This causes your master password to be remembered while your device is powered on. Similar to your phone&apos;s SIM lock, you only need to enter the password once after powering on.</string>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Remember my password</string>
<key>Key</key>
<string>rememberKeyPhrase</string>
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string></string>
<key>FooterText</key>
<string>Use this to save your master password in the key chain. This is somewhat less secure should your device get stolen, but it means you won&apos;t need to enter the master password anymore. You can compensate for the reduced security by going into General-&gt;Passcode Lock, disabling &quot;Simple Passcode&quot;, and setting a more secure passcode for your device.</string>
</dict> </dict>
<dict> <dict>
<key>DefaultValue</key> <key>DefaultValue</key>
@ -40,17 +74,25 @@
<key>Key</key> <key>Key</key>
<string>storeKeyPhrase</string> <string>storeKeyPhrase</string>
<key>Title</key> <key>Title</key>
<string>Store master password</string> <string>Store my password</string>
<key>Type</key> <key>Type</key>
<string>PSToggleSwitchSpecifier</string> <string>PSToggleSwitchSpecifier</string>
</dict> </dict>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string></string>
<key>FooterText</key>
<string>Turn this on if you want to change the master password. Start the application and it will ask you for a new master password. All passwords will change to those for the new master password.</string>
</dict>
<dict> <dict>
<key>Type</key> <key>Type</key>
<string>PSToggleSwitchSpecifier</string> <string>PSToggleSwitchSpecifier</string>
<key>Title</key> <key>Title</key>
<string>Remember master password</string> <string>Change my password</string>
<key>Key</key> <key>Key</key>
<string>rememberKeyPhrase</string> <string>forgetKeyPhrase</string>
<key>DefaultValue</key> <key>DefaultValue</key>
<false/> <false/>
</dict> </dict>