2
0

Improvements to password import.

[FIXED]     Importing of mpsites with passwords showing for
            stored password types.
[FIXED]     Don't try to show mail composition dialog when the user has
            no mail account configured.  This will crash.  Instead,
            show a friendly popup explaining things.
[IMPROVED]  Message of password export emails.
[FIXED]     Hierarchy of MPUnlockVC so password field becomes touchable.
This commit is contained in:
Maarten Billemont 2012-06-22 16:52:33 +02:00
parent b7e91358be
commit 4d4ba3425e
9 changed files with 136 additions and 86 deletions

View File

@ -16,6 +16,4 @@
- (void)storeSavedKeyFor:(MPUserEntity *)user;
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
- (NSData *)keyWithLength:(NSUInteger)keyLength;
@end

View File

@ -169,9 +169,4 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
return YES;
}
- (NSData *)keyWithLength:(NSUInteger)keyLength {
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
}
@end

View File

@ -217,9 +217,10 @@
if (!headerPattern || !sitePattern)
return MPImportResultInternalError;
NSData *key = nil;
NSString *keyIDHex = nil, *userName = nil;
MPUserEntity *user = nil;
BOOL headerStarted = NO, headerEnded = NO;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
@ -246,19 +247,24 @@
}
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([key isEqualToString:@"User Name"]) {
userName = value;
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
userName = headerValue;
key = keyForPassword(password, userName);
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
}
if ([key isEqualToString:@"Key ID"]) {
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]])
if ([headerName isEqualToString:@"Key ID"]) {
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
return MPImportResultInvalidPassword;
}
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
}
continue;
}
@ -337,7 +343,10 @@
element.uses = uses;
element.lastUsed = lastUsed;
if ([exportContent length])
[element importContent:exportContent];
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
}
[self saveContext];

View File

@ -21,7 +21,8 @@
- (NSUInteger)use;
- (NSString *)exportContent;
- (void)importContent:(NSString *)content;
- (void)importProtectedContent:(NSString *)protectedContent;
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
@end

View File

@ -50,7 +50,11 @@
return nil;
}
- (void)importContent:(NSString *)content {
- (void)importProtectedContent:(NSString *)content {
}
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
}
@ -108,7 +112,18 @@
- (id)content {
return [self contentUsingKey:[MPAppDelegate get].key];
}
- (void)setContent:(id)content {
[self setContent:content usingKey:[MPAppDelegate get].key];
}
- (id)contentUsingKey:(NSData *)key {
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate)
@ -116,15 +131,16 @@
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content {
- (void)setContent:(id)content usingKey:(NSData *)key {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
padding:YES];
assert(self.type & MPElementTypeClassStored);
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
@ -144,9 +160,14 @@
return [self.contentObject encodeBase64];
}
- (void)importContent:(NSString *)content {
- (void)importProtectedContent:(NSString *)protectedContent {
self.contentObject = [content decodeBase64];
self.contentObject = [protectedContent decodeBase64];
}
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
[self setContent:clearContent usingKey:key];
}
@end

View File

@ -78,6 +78,7 @@ typedef enum {
#define MPNotificationElementUsed @"MPNotificationElementUsed"
NSData *keyForPassword(NSString *password, NSString *username);
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
NSData *keyIDForPassword(NSString *password, NSString *username);
NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type);

View File

@ -30,6 +30,13 @@ NSData *keyForPassword(NSString *password, NSString *username) {
return key;
}
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
}
NSData *keyIDForPassword(NSString *password, NSString *username) {
return keyIDForKey(keyForPassword(password, username));

View File

@ -202,8 +202,6 @@
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
#endif
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[super application:application didFinishLaunchingWithOptions:launchOptions];
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
@ -419,7 +417,7 @@
// Safe Export
[self exportShowPasswords:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
// Safe Export
// Show Passwords
[self exportShowPasswords:YES];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
} otherTitles:nil];
@ -427,17 +425,38 @@
- (void)exportShowPasswords:(BOOL)showPasswords {
if (![MFMailComposeViewController canSendMail]) {
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
message:
@"Your device is not yet set up for sending mail.\n"
@"Close Master Password, go into Settings and add a Mail account."
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil
cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
return;
}
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
NSString *message;
if (showPasswords)
message = PearlString(
@"Export of %@'s Master Password sites with passwords visible.\n"
@"REMINDER: Make sure nobody else sees this file! All passwords are visible!\n",
self.activeUser.name);
message = PearlString(@"Export of Master Password sites with passwords included.\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
self.activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion);
else
message = PearlString(
@"Backup of %@'s Master Password sites.\n",
self.activeUser.name);
message = PearlString(@"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
self.activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion);
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11E53" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
<dependencies>
<deployment defaultVersion="1296" identifier="iOS"/>
<development defaultVersion="4200" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
</dependencies>
@ -828,54 +827,6 @@ L4m3P4sSw0rD</string>
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<view contentMode="scaleToFill" id="7cc-yu-i0m">
<rect key="frame" x="20" y="168" width="280" height="88"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
<rect key="frame" x="12" y="0.0" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
</imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
<textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
</connections>
</textField>
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="NvG-0R-eTZ">
<rect key="frame" x="35" y="0.0" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="3se-By-a9W">
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Incorrect password." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="gQ2-mB-BP4">
<rect key="frame" x="15" y="0.5" width="180" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
<rect key="frame" x="105" y="30" width="110" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
@ -904,6 +855,54 @@ L4m3P4sSw0rD</string>
<outletCollection property="gestureRecognizers" destination="9WS-yS-aqQ" appends="YES" id="B9k-bg-gqA"/>
</connections>
</scrollView>
<view contentMode="scaleToFill" id="7cc-yu-i0m">
<rect key="frame" x="20" y="168" width="280" height="88"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
<rect key="frame" x="12" y="0.0" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
</imageView>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
<rect key="frame" x="10" y="28" width="260" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="36"/>
<textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
<connections>
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
</connections>
</textField>
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="NvG-0R-eTZ">
<rect key="frame" x="35" y="0.0" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="3se-By-a9W">
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Incorrect password." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="gQ2-mB-BP4">
<rect key="frame" x="15" y="0.5" width="180" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="left" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" id="8s0-nT-Aoq">
<rect key="frame" x="90" y="289" width="140" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
@ -942,7 +941,7 @@ L4m3P4sSw0rD</string>
<outlet property="deleteTip" destination="DBJ-Qi-ZcF" id="VXD-Zc-UYi"/>
<outlet property="nameLabel" destination="0NM-NI-7UR" id="GBg-Ry-sqj"/>
<outlet property="oldNameLabel" destination="8s0-nT-Aoq" id="plu-1H-MVc"/>
<outlet property="passwordField" destination="rTR-7Q-X8o" id="CDA-iP-kCm"/>
<outlet property="passwordField" destination="rTR-7Q-X8o" id="Vsn-uO-4lO"/>
<outlet property="passwordTipLabel" destination="gQ2-mB-BP4" id="aHU-tn-duI"/>
<outlet property="passwordTipView" destination="NvG-0R-eTZ" id="4Mx-TL-yfu"/>
<outlet property="passwordView" destination="7cc-yu-i0m" id="WoF-Ab-PPC"/>