//
// BlorPasswordRetriever.m
// Notation
//
// Created by Zachary Schneirov on 12/13/06.
/*Copyright (c) 2010, Zachary Schneirov. All rights reserved.
This file is part of Notational Velocity.
Notational Velocity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Notational Velocity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Notational Velocity. If not, see . */
#import "BlorPasswordRetriever.h"
#import "NoteObject.h"
#import "GlobalPrefs.h"
#import "NSData_transformations.h"
#import "AttributedPlainText.h"
#import "NotationPrefs.h"
#include "idea_ossl.h"
@implementation BlorPasswordRetriever
- (id)initWithBlor:(NSString*)blorPath {
if ([super init]) {
path = [blorPath retain];
couldRetrieveFromKeychain = NO;
//read hash (first 20 bytes) of file
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path];
hashData = [[handle readDataOfLength:20] retain];
[handle closeFile];
if (!hashData || [hashData length] < 20)
return nil;
}
return self;
}
- (IBAction)cancelAction:(id)sender {
[NSApp stopModalWithCode:0];
[window close];
if (![[GlobalPrefs defaultPrefs] triedToImportBlor])
NSRunAlertPanel(NSLocalizedString(@"Note Importing Cancelled", nil),
NSLocalizedString(@"You can import your old notes at any time by choosing quotemarkImport...quotemark from the quotemarkNotequotemark menu and selecting your NotationalDatabase.blor file.",nil),
NSLocalizedString(@"OK",nil), nil, nil);
}
- (IBAction)importAction:(id)sender {
//check pw against hash and defaultCStringEncoding
NSData *passData = [[passphraseField stringValue] dataUsingEncoding:[NSString defaultCStringEncoding] allowLossyConversion:NO];
if ([[passData SHA1Digest] isEqualToData:hashData]) {
[NSApp stopModalWithCode:1];
[window close];
} else {
NSBeginAlertSheet(NSLocalizedString(@"Sorry, you entered an incorrect passphrase.",nil), NSLocalizedString(@"OK",nil),
nil, nil, window, nil, NULL, NULL, NULL, NSLocalizedString(@"Please try again.",nil));
}
}
- (NSData*)keychainPasswordData {
NSString *keychainAccountString = [[path stringByAbbreviatingWithTildeInPath] lowercaseString];
if ([keychainAccountString length] > 255) keychainAccountString = [keychainAccountString substringToIndex:255];
const char *keychainAccountCString = [[keychainAccountString dataUsingEncoding:
[NSString defaultCStringEncoding] allowLossyConversion:YES] bytes];
UInt32 len;
void *p = (void *)calloc(256, sizeof(char));
if (kcfindgenericpassword("NV", keychainAccountCString, 255, p, &len, NULL) != noErr) {
free(p);
return NULL;
}
return [NSData dataWithBytesNoCopy:p length:len freeWhenDone:YES];
}
- (NSData*)validPasswordHashData {
[originalPasswordString release];
originalPasswordString = nil;
//try to get PW from keychain. if that fails, request from user
NSData *passwordData = [self keychainPasswordData];
if (passwordData && [[passwordData SHA1Digest] isEqualToData:hashData]) {
couldRetrieveFromKeychain = YES;
originalPasswordString = [[NSString alloc] initWithData:passwordData encoding:[NSString defaultCStringEncoding]];
return [passwordData BrokenMD5Digest];
}
//run dialog and grab PW
if (!window) {
if (![NSBundle loadNibNamed:@"BlorPasswordRetriever" owner:self]) {
NSLog(@"Failed to load BlorPasswordRetriever.nib");
NSBeep();
return NULL;
}
}
[helpStringField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Please enter the passphrase to import old notes at %@.",nil),
[path stringByAbbreviatingWithTildeInPath]]];
int result = [NSApp runModalForWindow:window];
NSString *passwordString = [passphraseField stringValue];
passwordData = [passwordString dataUsingEncoding:[NSString defaultCStringEncoding] allowLossyConversion:NO];
if (result && [[passwordData SHA1Digest] isEqualToData:hashData]) {
originalPasswordString = [passwordString copy];
[passphraseField setStringValue:@""];
return [passwordData BrokenMD5Digest];
}
[passphraseField setStringValue:@""];
return NULL;
}
- (NSString*)originalPasswordString {
return originalPasswordString;
}
- (BOOL)canRetrieveFromKeychain {
return couldRetrieveFromKeychain;
}
- (void)awakeFromNib {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:)
name:NSControlTextDidChangeNotification object:passphraseField];
}
- (void)textDidChange:(NSNotification *)aNotification {
[importButton setEnabled:([[passphraseField stringValue] length] > 0)];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[hashData release];
[path release];
[originalPasswordString release];
[super dealloc];
}
@end
@implementation BlorNoteEnumerator
- (id)initWithBlor:(NSString*)blorPath passwordHashData:(NSData*)passwordHashData {
if ([super init]) {
path = [blorPath retain];
if (!(keyData = [passwordHashData retain]))
return nil;
if (!(blorData = [[NSMutableData dataWithContentsOfFile:path] retain]))
return nil;
if ([blorData length] < 28) {
NSLog(@"read data is too small (%d) to hold any notes!", [blorData length]);
return nil;
}
successfullyReadNoteCount = 0;
suspectedNoteCount = *(unsigned int*)([blorData bytes] + 20);
suspectedNoteCount = CFSwapInt32BigToHost(suspectedNoteCount);
currentByteOffset = 24;
//read past the # of notes marker--we're just going to read as many notes as possible
}
return self;
}
- (void)dealloc {
[blorData release];
[keyData release];
[super dealloc];
}
- (void)decryptNextBytesOfLength:(long)length {
unsigned char iv[] = {
0x50, 0x7E, 0x4C, 0x17,
0x99, 0x3A, 0x07, 0x01
};
int num = 0;
IDEA_KEY_SCHEDULE enc;
idea_set_encrypt_key([keyData bytes], &enc);
unsigned char *bytes = [blorData mutableBytes] + currentByteOffset;
idea_cfb64_encrypt(bytes, bytes, length, &enc, iv, &num, IDEA_DECRYPT);
}
- (unsigned int)suspectedNoteCount {
return suspectedNoteCount;
}
#define ASSERT_CAN_READ_BYTE_COUNT(n) do { \
if (!((n) + currentByteOffset <= [blorData length])) { \
NSLog(@"Attempted to read %d bytes past the length of the blor!", ((n) + currentByteOffset) - [blorData length]);\
return nil;\
} \
} while (0)
- (id)nextNote {
int titleBytesLength;
//read length of title
ASSERT_CAN_READ_BYTE_COUNT(sizeof(titleBytesLength));
titleBytesLength = *(int*)([blorData bytes] + currentByteOffset);
titleBytesLength = CFSwapInt32BigToHost(titleBytesLength);
currentByteOffset += sizeof(titleBytesLength);
//read/decrypt title
ASSERT_CAN_READ_BYTE_COUNT(titleBytesLength);
[self decryptNextBytesOfLength:titleBytesLength];
NSData *titleData = [NSData dataWithBytesNoCopy:[blorData mutableBytes] + currentByteOffset length:titleBytesLength freeWhenDone:NO];
NSString *titleString = [[NSString alloc] initWithData:titleData encoding:NSUnicodeStringEncoding];
currentByteOffset += titleBytesLength;
int bodyBufferBytesLength, bodyBytesLength;
//read lengths of body
ASSERT_CAN_READ_BYTE_COUNT(sizeof(bodyBufferBytesLength));
bodyBufferBytesLength = *(int*)([blorData bytes] + currentByteOffset);
bodyBufferBytesLength = CFSwapInt32BigToHost(bodyBufferBytesLength);
currentByteOffset += sizeof(bodyBufferBytesLength);
ASSERT_CAN_READ_BYTE_COUNT(sizeof(bodyBytesLength));
bodyBytesLength = *(int*)([blorData bytes] + currentByteOffset);
bodyBytesLength = CFSwapInt32BigToHost(bodyBytesLength);
currentByteOffset += sizeof(bodyBytesLength);
//read/decrypt body
ASSERT_CAN_READ_BYTE_COUNT(bodyBytesLength);
[self decryptNextBytesOfLength:bodyBytesLength];
NSData *bodyData = [NSData dataWithBytesNoCopy:[blorData mutableBytes] + currentByteOffset length:bodyBytesLength freeWhenDone:NO];
NSString *bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUnicodeStringEncoding];
currentByteOffset += bodyBufferBytesLength;
//do we need to assert bodyBufferBytesLength, too?
//create new note and return it
NSMutableAttributedString *attributedBody = [[NSMutableAttributedString alloc] initWithString:bodyString
attributes:[[GlobalPrefs defaultPrefs] noteBodyAttributes]];
[attributedBody addLinkAttributesForRange:NSMakeRange(0, [attributedBody length])];
[attributedBody addStrikethroughNearDoneTagsForRange:NSMakeRange(0, [attributedBody length])];
[attributedBody addAttributesForMarkdownHeadingLinesInRange:NSMakeRange(0, [attributedBody length])];
NoteObject *note = [[NoteObject alloc] initWithNoteBody:attributedBody title:titleString delegate:nil format:SingleDatabaseFormat labels:nil];
[bodyString release];
[attributedBody release];
[titleString release];
successfullyReadNoteCount++;
return [note autorelease];
}
@end