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
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 43 KiB |
@ -6,6 +6,20 @@
|
||||
objectVersion = 46;
|
||||
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 */
|
||||
DA007F5214B24DCD00251337 /* OPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA007F5114B24DCD00251337 /* OPConfig.m */; };
|
||||
DA007F5514B25EE100251337 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA007F5414B25EE100251337 /* ciphers.plist */; };
|
||||
@ -656,6 +670,13 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
DA6556E114D55C4400841C99 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DA6556DC14D55C1500841C99;
|
||||
remoteInfo = InfoPlist;
|
||||
};
|
||||
DAC63281148681190075AEA5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
|
||||
@ -1738,8 +1759,6 @@
|
||||
DA5BFA39147E415C00F98B1E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */,
|
||||
DAC632871486D95D0075AEA5 /* Security.framework */,
|
||||
DA5BFA50147E415C00F98B1E /* OnePassword */,
|
||||
DAC77CAF148291A600BCF976 /* Pearl */,
|
||||
DAC6325F1486805C0075AEA5 /* uicolor-utilities */,
|
||||
@ -1763,6 +1782,8 @@
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */,
|
||||
DAC632871486D95D0075AEA5 /* Security.framework */,
|
||||
DA5BFA48147E415C00F98B1E /* UIKit.framework */,
|
||||
DA5BFA4A147E415C00F98B1E /* Foundation.framework */,
|
||||
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */,
|
||||
@ -2271,6 +2292,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "OnePassword" */;
|
||||
buildPhases = (
|
||||
DA6556E314D55F3000841C99 /* ShellScript */,
|
||||
DA5BFA40147E415C00F98B1E /* Sources */,
|
||||
DA5BFA41147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA42147E415C00F98B1E /* Resources */,
|
||||
@ -2278,6 +2300,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
DA6556E214D55C4400841C99 /* PBXTargetDependency */,
|
||||
DAC63282148681190075AEA5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = OnePassword;
|
||||
@ -2365,6 +2388,7 @@
|
||||
DAC77CAC148291A600BCF976 /* Pearl */,
|
||||
DAC6325C1486805C0075AEA5 /* uicolor-utilities */,
|
||||
DAC6326B148680650075AEA5 /* jrswizzle */,
|
||||
DA6556DC14D55C1500841C99 /* InfoPlist */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -2926,6 +2950,37 @@
|
||||
};
|
||||
/* 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 */
|
||||
DA5BFA40147E415C00F98B1E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@ -3006,6 +3061,11 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
DA6556E214D55C4400841C99 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DA6556DC14D55C1500841C99 /* InfoPlist */;
|
||||
targetProxy = DA6556E114D55C4400841C99 /* PBXContainerItemProxy */;
|
||||
};
|
||||
DAC63282148681190075AEA5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DAC77CAC148291A600BCF976 /* Pearl */;
|
||||
@ -3114,6 +3174,20 @@
|
||||
};
|
||||
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 */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@ -3209,6 +3283,14 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
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" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -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"/>
|
||||
</tableViewController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="962" y="182"/>
|
||||
<point key="canvasLocation" x="996" y="182"/>
|
||||
</scene>
|
||||
<scene sceneID="U26-Zf-euQ">
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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>
|
||||
</subviews>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
|
||||
</view>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="180"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="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"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="jCg-t7-RuK">
|
||||
<rect key="frame" x="20" y="60" width="260" height="70"/>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" id="jCg-t7-RuK">
|
||||
<rect key="frame" x="20" y="60" width="260" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<string key="text">The password for apple.com has changed.
|
||||
@ -567,7 +570,7 @@ L4m3P4sSw0rD</string>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.14901960784313725" green="0.16470588235294117" blue="0.1803921568627451" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</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">
|
||||
<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"/>
|
||||
@ -610,6 +613,30 @@ L4m3P4sSw0rD</string>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="182"/>
|
||||
</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">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@ -632,6 +659,7 @@ L4m3P4sSw0rD</string>
|
||||
<resources>
|
||||
<image name="Content-Backdrop.png" width="480" height="480"/>
|
||||
<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_edit.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_display.png" width="300" height="86"/>
|
||||
</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">
|
||||
<simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/>
|
||||
<simulatedOrientationMetrics key="orientation"/>
|
||||
|
@ -10,14 +10,16 @@
|
||||
|
||||
@interface OPAppDelegate : AbstractAppDelegate
|
||||
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
||||
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
@property (strong, nonatomic) NSString *keyPhrase;
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
||||
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
@property (strong, nonatomic) NSString *keyPhrase;
|
||||
@property (strong, nonatomic) NSData *keyPhraseHash;
|
||||
@property (strong, nonatomic) NSString *keyPhraseHashHex;
|
||||
|
||||
+ (OPAppDelegate *)get;
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
+ (NSManagedObjectModel *)managedObjectModel;
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
|
||||
- (void)saveContext;
|
||||
- (NSURL *)applicationDocumentsDirectory;
|
||||
|
@ -15,19 +15,27 @@
|
||||
+ (NSDictionary *)keyPhraseQuery;
|
||||
+ (NSDictionary *)keyPhraseHashQuery;
|
||||
|
||||
- (void)loadKeyPhrase;
|
||||
- (void)forgetKeyPhrase;
|
||||
- (void)loadStoredKeyPhrase;
|
||||
- (void)askKeyPhrase;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OPAppDelegate
|
||||
|
||||
@synthesize managedObjectContext = __managedObjectContext;
|
||||
@synthesize managedObjectModel = __managedObjectModel;
|
||||
@synthesize managedObjectContext = __managedObjectContext;
|
||||
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
|
||||
|
||||
@synthesize keyPhrase = _keyPhrase;
|
||||
@synthesize keyPhraseHash = _keyPhraseHash;
|
||||
@synthesize keyPhraseHashHex = _keyPhraseHashHex;
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
#ifdef DEBUG
|
||||
[Logger get].autoprintLevel = LogLevelDebug;
|
||||
[Logger get].autoprintLevel = LogLevelTrace;
|
||||
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
|
||||
#endif
|
||||
}
|
||||
@ -37,7 +45,7 @@
|
||||
static NSDictionary *OPKeyPhraseQuery = nil;
|
||||
if (!OPKeyPhraseQuery)
|
||||
OPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhrase"
|
||||
attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
|
||||
forKey:(__bridge id)kSecAttrService]
|
||||
matches:nil];
|
||||
|
||||
@ -49,7 +57,7 @@
|
||||
static NSDictionary *OPKeyPhraseHashQuery = nil;
|
||||
if (!OPKeyPhraseHashQuery)
|
||||
OPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhraseHash"
|
||||
attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
|
||||
forKey:(__bridge id)kSecAttrService]
|
||||
matches:nil];
|
||||
|
||||
@ -89,31 +97,77 @@
|
||||
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
|
||||
|
||||
/*
|
||||
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
|
||||
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
|
||||
|
||||
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
|
||||
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
|
||||
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
|
||||
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
|
||||
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
|
||||
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
|
||||
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
|
||||
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
- (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]) {
|
||||
// Key phrase is stored in keychain. Load it.
|
||||
dbg(@"Loading master key phrase from key chain.");
|
||||
@ -127,59 +181,55 @@
|
||||
dbg(@"Deleting master key phrase from key chain.");
|
||||
[KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.keyPhrase) {
|
||||
// Key phrase is not known. Ask user to set/specify it.
|
||||
dbg(@"Key phrase not known. Will ask user.");
|
||||
- (void)askKeyPhrase {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseHashQuery]];
|
||||
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseHashQuery]];
|
||||
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
|
||||
|
||||
AlertViewController *keyPhraseAlert = [[AlertViewController alloc] initQuestionWithTitle:@"One Password"
|
||||
message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:"
|
||||
tappedButtonBlock:
|
||||
^(NSInteger buttonIndex, NSString *answer) {
|
||||
if (!buttonIndex)
|
||||
exit(0);
|
||||
[AlertViewController showAlertWithTitle:@"One Password"
|
||||
message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:"
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
tappedButtonBlock:
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
exit(0);
|
||||
|
||||
if (![answer length]) {
|
||||
// User didn't enter a key phrase.
|
||||
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
|
||||
message:@"No master password entered."
|
||||
tappedButtonBlock:
|
||||
^(NSInteger buttonIndex) {
|
||||
exit(0);
|
||||
} cancelTitle:@"Quit" otherTitles:nil];
|
||||
}
|
||||
NSString *answer = [alert textFieldAtIndex:0].text;
|
||||
if (![answer length]) {
|
||||
// User didn't enter a key phrase.
|
||||
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
|
||||
message:@"No master password entered."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
exit(0);
|
||||
} cancelTitle:@"Quit" otherTitles:nil];
|
||||
}
|
||||
|
||||
NSData *answerHash = [answer hashWith:PearlDigestSHA512];
|
||||
if (keyPhraseHash)
|
||||
// A key phrase hash is known -> a key phrase is set.
|
||||
// Make sure the user's entered key phrase matches it.
|
||||
if (![keyPhraseHash isEqual:answerHash]) {
|
||||
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, answerHash);
|
||||
NSData *answerHash = [answer hashWith:PearlDigestSHA512];
|
||||
if (keyPhraseHash)
|
||||
// A key phrase hash is known -> a key phrase is set.
|
||||
// Make sure the user's entered key phrase matches it.
|
||||
if (![keyPhraseHash isEqual:answerHash]) {
|
||||
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, answerHash);
|
||||
|
||||
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
|
||||
message:@"Incorrect master password."
|
||||
tappedButtonBlock:
|
||||
^(NSInteger buttonIndex) {
|
||||
exit(0);
|
||||
} cancelTitle:@"Quit" otherTitles:nil];
|
||||
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
|
||||
message:@"Incorrect master password."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
exit(0);
|
||||
} cancelTitle:@"Quit" otherTitles:nil];
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.keyPhrase = answer;
|
||||
} 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];
|
||||
});
|
||||
}
|
||||
self.keyPhrase = answer;
|
||||
} cancelTitle:@"Quit" otherTitles:@"Unlock", nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
@ -228,10 +278,12 @@
|
||||
_keyPhrase = keyPhrase;
|
||||
|
||||
if (keyPhrase) {
|
||||
NSData *keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
|
||||
dbg(@"Updating master key phrase hash to: %@.", keyPhraseHash);
|
||||
self.keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
|
||||
self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
|
||||
|
||||
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
|
||||
[KeyChain addOrUpdateItemForQuery:[OPAppDelegate keyPhraseHashQuery]
|
||||
withAttributes:[NSDictionary dictionaryWithObject:keyPhraseHash
|
||||
withAttributes:[NSDictionary dictionaryWithObject:self.keyPhraseHash
|
||||
forKey:(__bridge id)kSecValueData]];
|
||||
if ([[OPConfig get].storeKeyPhrase boolValue]) {
|
||||
dbg(@"Storing master key phrase in key chain.");
|
||||
|
@ -11,6 +11,7 @@
|
||||
@property (nonatomic, retain) NSNumber *dataStoreError;
|
||||
@property (nonatomic, retain) NSNumber *storeKeyPhrase;
|
||||
@property (nonatomic, retain) NSNumber *rememberKeyPhrase;
|
||||
@property (nonatomic, retain) NSNumber *forgetKeyPhrase;
|
||||
@property (nonatomic, retain) NSNumber *helpHidden;
|
||||
|
||||
+ (OPConfig *)get;
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
@implementation OPConfig
|
||||
|
||||
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden;
|
||||
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, forgetKeyPhrase, helpHidden;
|
||||
|
||||
|
||||
- (id)init {
|
||||
@ -22,6 +22,7 @@
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberKeyPhrase)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(forgetKeyPhrase)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||
nil]];
|
||||
|
||||
|
@ -13,14 +13,12 @@
|
||||
@interface OPElementEntity : NSManagedObject
|
||||
|
||||
@property (nonatomic, retain) NSString *name;
|
||||
@property (nonatomic, retain) NSString *mpHashHex;
|
||||
@property (nonatomic) int16_t type;
|
||||
@property (nonatomic) int16_t uses;
|
||||
@property (nonatomic) NSTimeInterval lastUsed;
|
||||
@property (nonatomic, retain) NSString *contentUTI;
|
||||
@property (nonatomic) int16_t contentType;
|
||||
|
||||
- (void)use;
|
||||
- (id)content;
|
||||
- (NSString *)contentDescription;
|
||||
|
||||
@end
|
||||
|
@ -12,11 +12,10 @@
|
||||
@implementation OPElementEntity
|
||||
|
||||
@dynamic name;
|
||||
@dynamic mpHashHex;
|
||||
@dynamic type;
|
||||
@dynamic uses;
|
||||
@dynamic lastUsed;
|
||||
@dynamic contentUTI;
|
||||
@dynamic contentType;
|
||||
|
||||
- (void)use {
|
||||
|
||||
@ -29,9 +28,15 @@
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
|
||||
}
|
||||
|
||||
- (NSString *)contentDescription {
|
||||
- (NSString *)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
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
- (id)content {
|
||||
|
||||
assert(self.type & OPElementTypeClassCalculated);
|
||||
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
#import "OPElementEntity.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) IBOutlet OPSearchDelegate *searchResultsController;
|
||||
|
@ -84,7 +84,7 @@
|
||||
|
||||
// Because IB's edit button doesn't auto-toggle self.editable like editButtonItem does.
|
||||
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) {
|
||||
if (![OPAppDelegate get].keyPhrase) {
|
||||
self.activeElement = nil;
|
||||
@ -171,9 +171,10 @@
|
||||
self.passwordCounter.text = [NSString stringWithFormat:@"%d", ((OPElementGeneratedEntity *) self.activeElement).counter];
|
||||
|
||||
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(), ^{
|
||||
self.contentField.text = contentDescription;
|
||||
self.contentField.text = description;
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -223,7 +224,7 @@
|
||||
return;
|
||||
|
||||
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content
|
||||
forPasteboardType:self.activeElement.contentUTI];
|
||||
forPasteboardType:(id)kUTTypeUTF8PlainText];
|
||||
|
||||
[self showContentTip:@"Copied!" withIcon:nil];
|
||||
}
|
||||
@ -242,9 +243,9 @@
|
||||
- (void)updateElement:(void (^)(void))updateElement {
|
||||
|
||||
// Update password counter.
|
||||
NSString *oldPassword = self.activeElement.contentDescription;
|
||||
NSString *oldPassword = self.activeElement.description;
|
||||
updateElement();
|
||||
NSString *newPassword = self.activeElement.contentDescription;
|
||||
NSString *newPassword = self.activeElement.description;
|
||||
[self updateAnimated:YES];
|
||||
|
||||
// Show new and old password.
|
||||
@ -304,10 +305,9 @@
|
||||
OPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromOPElementType(type)
|
||||
inManagedObjectContext:[OPAppDelegate managedObjectContext]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.mpHashHex = self.activeElement.mpHashHex;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
newElement.contentUTI = self.activeElement.contentUTI;
|
||||
newElement.contentType = self.activeElement.contentType;
|
||||
|
||||
[[OPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
@ -315,7 +315,7 @@
|
||||
|
||||
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];
|
||||
}];
|
||||
}
|
||||
@ -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
|
||||
|
@ -61,15 +61,18 @@
|
||||
|
||||
- (void)update {
|
||||
|
||||
NSString *text = self.searchDisplayController.searchBar.text;
|
||||
if (!text)
|
||||
text = @"";
|
||||
NSString *query = self.searchDisplayController.searchBar.text;
|
||||
if (!query)
|
||||
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:
|
||||
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
|
||||
[fetchRequest setPredicate:
|
||||
[NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", text]];
|
||||
|
||||
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[OPAppDelegate managedObjectContext]
|
||||
@ -196,7 +199,10 @@
|
||||
// "New" section.
|
||||
element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([OPElementGeneratedEntity class])
|
||||
inManagedObjectContext:[OPAppDelegate managedObjectContext]];
|
||||
assert([element isKindOfClass:ClassFromOPElementType(element.type)]);
|
||||
|
||||
element.name = self.searchDisplayController.searchBar.text;
|
||||
element.mpHashHex = [OPAppDelegate get].keyPhraseHashHex;
|
||||
}
|
||||
|
||||
[self.delegate didSelectElement:element];
|
||||
|
@ -37,13 +37,15 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>[auto]</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<string>[auto]</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright ©2011-2012, Lyndir</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>MainStoryboard_iPhone</string>
|
||||
<key>UIMainStoryboardFile~ipad</key>
|
||||
|
@ -1,9 +1,8 @@
|
||||
<?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">
|
||||
<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="mpHashHex" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Integer 16" defaultValueString="257" 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">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<fetchRequest name="OPSearchElement" entity="OPElementEntity" predicateString="name BEGINSWITH "$query""/>
|
||||
<fetchRequest name="OPSearchElement" entity="OPElementEntity" predicateString="name BEGINSWITH[cd] $query AND mpHashHex == $mpHashHex"/>
|
||||
<elements>
|
||||
<element name="OPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
|
||||
<element name="OPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
@ -9,7 +9,7 @@
|
||||
font: 16px "Baskerville";
|
||||
}
|
||||
h1, h2 {
|
||||
margin-top: 1em;
|
||||
margin-top: 1.5em;
|
||||
padding-top: 1em;
|
||||
font-family: inherit;
|
||||
font-weight: bold;
|
||||
@ -20,12 +20,19 @@
|
||||
i {
|
||||
font-weight: bold;
|
||||
}
|
||||
q {
|
||||
font-style: italic;
|
||||
}
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 1.4em;
|
||||
margin: -0.2em 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
a, a:link {
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<script src="jquery-1.6.1.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
@ -70,5 +77,100 @@
|
||||
This is useful, for example, when you've had to share the password with somebody else.
|
||||
</p>
|
||||
|
||||
<h2 id="faq">— F.A.Q. —</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>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 152 KiB |
@ -4,6 +4,14 @@
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>FooterText</key>
|
||||
<string>http://masterpassword.lyndir.com</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
@ -11,6 +19,8 @@
|
||||
<string>Version</string>
|
||||
<key>Key</key>
|
||||
<string>version</string>
|
||||
<key>DefaultValue</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
@ -19,6 +29,8 @@
|
||||
<string>Build</string>
|
||||
<key>Key</key>
|
||||
<string>build</string>
|
||||
<key>DefaultValue</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
@ -27,12 +39,34 @@
|
||||
<string>Copyright</string>
|
||||
<key>Key</key>
|
||||
<string>copyright</string>
|
||||
<key>DefaultValue</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<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'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't need to enter the master password anymore. You can compensate for the reduced security by going into General->Passcode Lock, disabling "Simple Passcode", and setting a more secure passcode for your device.</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
@ -40,17 +74,25 @@
|
||||
<key>Key</key>
|
||||
<string>storeKeyPhrase</string>
|
||||
<key>Title</key>
|
||||
<string>Store master password</string>
|
||||
<string>Store my password</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</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>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Remember master password</string>
|
||||
<string>Change my password</string>
|
||||
<key>Key</key>
|
||||
<string>rememberKeyPhrase</string>
|
||||
<string>forgetKeyPhrase</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
|