aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax <post@wickenrode.com>2015-10-14 21:58:51 +0200
committerMax <post@wickenrode.com>2015-10-14 21:58:51 +0200
commit264babe2f0d1b5c7713c1a9df0417e4ec5b84bd2 (patch)
tree36451fb3fea5755abcfe19af290a9babe4dc8d14
parent8f5f361da4cc30a72641c8ae72db5cce03ce234f (diff)
downloadsequelpro-264babe2f0d1b5c7713c1a9df0417e4ec5b84bd2.tar.gz
sequelpro-264babe2f0d1b5c7713c1a9df0417e4ec5b84bd2.tar.bz2
sequelpro-264babe2f0d1b5c7713c1a9df0417e4ec5b84bd2.zip
Add code to verify that a SSL key file actually contains a usable RSA key
-rw-r--r--Source/SPConnectionController.m48
-rw-r--r--Source/SPDataAdditions.h9
-rw-r--r--Source/SPDataAdditions.m53
-rw-r--r--UnitTests/SPDataAdditionsTests.m92
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj4
5 files changed, 205 insertions, 1 deletions
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 <https://github.com/sequelpro/sequelpro>
+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 <stdlib.h>
#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 */,