From 264babe2f0d1b5c7713c1a9df0417e4ec5b84bd2 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 Oct 2015 21:58:51 +0200 Subject: Add code to verify that a SSL key file actually contains a usable RSA key --- Source/SPConnectionController.m | 48 ++++++++++++++++++- Source/SPDataAdditions.h | 9 ++++ Source/SPDataAdditions.m | 53 +++++++++++++++++++++ UnitTests/SPDataAdditionsTests.m | 92 ++++++++++++++++++++++++++++++++++++ sequel-pro.xcodeproj/project.pbxproj | 4 ++ 5 files changed, 205 insertions(+), 1 deletion(-) diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index dd677ee4..1df7f9b9 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -431,7 +431,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, keySelectionPanel = [[NSOpenPanel openPanel] retain]; // retain/release needed on OS X ≤ 10.6 according to Apple doc [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; [keySelectionPanel setAccessoryView:accessoryView]; - + [keySelectionPanel setDelegate:self]; [keySelectionPanel beginSheetModalForWindow:[dbDocument parentWindow] completionHandler:^(NSInteger returnCode) { NSString *abbreviatedFileName = [[[keySelectionPanel URL] path] stringByAbbreviatingWithTildeInPath]; @@ -486,6 +486,52 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, #endif } +- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError +{ + if([keySelectionPanel accessoryView] == sslKeyFileLocationHelp) { + // mysql limits yaSSL to PEM format files and + // yaSSL only supports RSA type keys, with the exact string below on a single line + NSError *err = nil; + NSData *file = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&err]; + if(err) { + *outError = err; + return NO; + } + __block BOOL rsaStart = NO; + __block BOOL rsaEnd = NO; + [file enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + if(!rsaStart) { + const char rsaHead[] = "-----BEGIN RSA PRIVATE KEY-----"; + size_t rsaLen = strlen(rsaHead); + if(line.length != rsaLen) return; + if(memcmp(rsaHead, ([file bytes]+line.location), rsaLen) == 0) { + rsaStart = YES; + } + } + else { + const char rsaFoot[] = "-----END RSA PRIVATE KEY-----"; + size_t rsaLen = strlen(rsaFoot); + if(line.length != rsaLen) return; + if(memcmp(rsaFoot, ([file bytes]+line.location), rsaLen) == 0) { + rsaEnd = YES; + *stop = YES; + } + } + }]; + + if(rsaStart && rsaEnd) return YES; + + *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"“%@” is not a valid private key file.", @""),[url lastPathComponent]], + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure the file contains an RSA private key and is using PEM encoding.", @""), + NSURLErrorKey: url + }]; + return NO; + } + //unknown, accept by default + return YES; +} + /** * Show connection help webpage. */ diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 5158c270..cd8374f6 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -28,6 +28,13 @@ // // More info at +typedef NS_OPTIONS(NSUInteger, SPLineTerminator) { + SPLineTerminatorAny = 0, + SPLineTerminatorCR = 1, + SPLineTerminatorLF = 2, + SPLineTerminatorCRLF = 4, +}; + @interface NSData (SPDataAdditions) - (NSData *)sha1Hash; @@ -46,4 +53,6 @@ - (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding; - (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block; + @end diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 8b2207c5..65605577 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -39,6 +39,11 @@ #include #import "SPFunctions.h" +/** Limit an NSUInteger to unsigned 32 bit max. + * @return Whatever is smaller: UINT32_MAX or i + * + * This is pretty much a NOOP on 32 bit platforms. + */ uint32_t LimitUInt32(NSUInteger i); #pragma mark - @@ -433,6 +438,54 @@ uint32_t LimitUInt32(NSUInteger i); return string; } +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block +{ + if(lbChars == SPLineTerminatorAny) lbChars = SPLineTerminatorCR|SPLineTerminatorLF|SPLineTerminatorCRLF; + + const uint8_t *bytes = [self bytes]; + NSUInteger length = [self length]; + + NSUInteger curStart = 0; + SPLineTerminator terminatorFound = 0; + NSUInteger i; + for (i = 0; i < length; i++) { + uint8_t chr = bytes[i]; + // if looking for cr and/or crlf we look for cr otherwise for lf + if(((lbChars & SPLineTerminatorCRLF) || (lbChars & SPLineTerminatorCR)) && chr == '\r') { + //if we are looking for CRLF check for the following LF + if((lbChars & SPLineTerminatorCRLF) && ((i+1) < length) && bytes[i+1] == '\n') { + terminatorFound = SPLineTerminatorCRLF; + } + //if we were looking for CR we've found one + else if((lbChars & SPLineTerminatorCR)) { + terminatorFound = SPLineTerminatorCR; + } + } + else if((lbChars & SPLineTerminatorLF) && chr == '\n') { + terminatorFound = SPLineTerminatorLF; + } + // no linebreak yet ? + if(!terminatorFound) continue; + + // found one. call the block. + BOOL stop = NO; + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + block(lineRange,&stop); + if(stop) return; + + // reset vars for next line + if(terminatorFound == SPLineTerminatorCRLF) i++; //skip the \n in CRLF + curStart = (i+1); + terminatorFound = 0; + } + // there could we one unterminated line left in buffer + if(curStart < i) { + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + BOOL iDontCare = NO; + block(lineRange,&iDontCare); + } +} + @end #pragma mark - diff --git a/UnitTests/SPDataAdditionsTests.m b/UnitTests/SPDataAdditionsTests.m index 812eb45d..c7a03f7f 100644 --- a/UnitTests/SPDataAdditionsTests.m +++ b/UnitTests/SPDataAdditionsTests.m @@ -40,6 +40,7 @@ - (void)testDataEncryptedWithKeyIV; - (void)testDataDecryptedWithPassword; - (void)testDataDecryptedWithKey; +- (void)testEnumerateLinesBreakingAt_withBlock; @end @@ -281,4 +282,95 @@ } } +- (void)testEnumerateLinesBreakingAt_withBlock +{ + //simple empty data + { + __block NSUInteger invocations = 0; + NSData *data = [NSData data]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + }]; + STAssertTrue(invocations==0, @"Empty data never invokes block"); + } + //simple unix file + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 3), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(4, 5), @"range of second line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==2, @"File with two lines, terminated with empty line"); + } + //simple windows file without ending empty line + { + const char inp[] = "A\r\nWindows\r\nfile"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 1), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(3, 7), @"range of second line"); + break; + case 2: + STAssertEquals(line, NSMakeRange(12, 4), @"range of third line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==3, @"File with three lines, CRLF, terminated with empty line"); + } + //empty lines with all 3 endings + { + const char inp[] = "\n\r\n\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 0), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(1, 0), @"range of second line"); + break; + case 2: + STAssertEquals(line, NSMakeRange(3, 0), @"range of third line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==3, @"LF, CRLF and CR mixed"); + } + //looking for specific line breaks only + { + const char inp[] = "foo\nbar\r\nbaz\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorCRLF withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 7), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(9, 4), @"range of second line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==2, @"other line breaks when only CRLF is expected"); + } +} + @end diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 3f78a15e..e836efe3 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -188,6 +188,8 @@ 503B02D11AE95DD40060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; }; 503B02D21AE95E010060CAB1 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; }; 503CDBB21ACDC204004F8A2F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 503CDBB11ACDC204004F8A2F /* Quartz.framework */; }; + 505F568F1BCEE485007467DD /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; + 505F56901BCEE491007467DD /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */; }; 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; @@ -3078,6 +3080,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 505F56901BCEE491007467DD /* SPOSInfo.m in Sources */, + 505F568F1BCEE485007467DD /* SPFunctions.m in Sources */, 502D21F81BA50966000D4CE7 /* SPDataAdditions.m in Sources */, 502D21F61BA50710000D4CE7 /* SPDataAdditionsTests.m in Sources */, 503B02D21AE95E010060CAB1 /* SPConstants.m in Sources */, -- cgit v1.2.3