From ea39be538d4726b583e67748d6e3e7ffb0ce9026 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 21 Mar 2015 03:05:33 +0100 Subject: Add UI for custom SSL cipher lists See Preferences > Network. Note: If you play around with that too much and libmysql can no longer connect, don't hope for a meaningful error. It will rather be something like "protocol version mismatch". --- Source/SPNetworkPreferencePane.h | 5 +- Source/SPNetworkPreferencePane.m | 195 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) (limited to 'Source') diff --git a/Source/SPNetworkPreferencePane.h b/Source/SPNetworkPreferencePane.h index 37408c32..d980f175 100644 --- a/Source/SPNetworkPreferencePane.h +++ b/Source/SPNetworkPreferencePane.h @@ -37,16 +37,19 @@ * * Network preference pane controller. */ -@interface SPNetworkPreferencePane : SPPreferencePane +@interface SPNetworkPreferencePane : SPPreferencePane { IBOutlet NSView *sshClientPickerView; IBOutlet NSTextField *sshClientPath; IBOutlet NSView *hiddenFileView; + IBOutlet NSTableView *sslCipherView; @private NSAlert *_currentAlert; NSOpenPanel *_currentFilePanel; + NSMutableArray *sslCiphers; } - (IBAction)pickSSHClientViaFileBrowser:(id)sender; - (IBAction)pickSSHClient:(id)sender; +- (IBAction)resetCipherList:(id)sender; @end diff --git a/Source/SPNetworkPreferencePane.m b/Source/SPNetworkPreferencePane.m index 376ba39f..4f13c433 100644 --- a/Source/SPNetworkPreferencePane.m +++ b/Source/SPNetworkPreferencePane.m @@ -30,12 +30,33 @@ #import "SPNetworkPreferencePane.h" +static NSString *SPSSLCipherListMarkerItem = @"--"; +static NSString *SPSSLCipherPboardTypeName = @"SSLCipherPboardType"; + @interface SPNetworkPreferencePane (Private) - (void)updateHiddenFiles; +- (void)loadSSLCiphers; +- (void)storeSSLCiphers; ++ (NSArray *)defaultSSLCipherList; @end @implementation SPNetworkPreferencePane +- (instancetype)init +{ + self = [super init]; + if (self) { + sslCiphers = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc +{ + SPClear(sslCiphers); + [super dealloc]; +} + #pragma mark - #pragma mark Preference pane protocol methods @@ -69,6 +90,16 @@ return NO; } +- (void)preferencePaneWillBeShown +{ + [self loadSSLCiphers]; + if(![[sslCipherView registeredDraggedTypes] containsObject:SPSSLCipherPboardTypeName]) + [sslCipherView registerForDraggedTypes:@[SPSSLCipherPboardTypeName]]; +} + +#pragma mark - +#pragma mark Custom SSH client methods + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if([SPHiddenKeyFileVisibilityKey isEqualTo:keyPath]) { @@ -131,4 +162,168 @@ SPClear(_currentAlert); } +#pragma mark - +#pragma mark SSL cipher list methods + +- (void)loadSSLCiphers +{ + NSArray *supportedCiphers = [SPNetworkPreferencePane defaultSSLCipherList]; + [sslCiphers removeAllObjects]; + + NSString *userCipherString = [prefs stringForKey:SPSSLCipherListKey]; + if(userCipherString) { + //expand user list + NSArray *userCipherList = [userCipherString componentsSeparatedByString:@":"]; + + //compare the users list to the valid list and only copy over valid items + for (NSString *userCipher in userCipherList) { + if (![supportedCiphers containsObject:userCipher] || [sslCiphers containsObject:userCipher]) { + SPLog(@"Unknown ssl cipher in users' list: %@",userCipher); + continue; + } + [sslCiphers addObject:userCipher]; + } + + //now we do the reverse and add valid ciphers that are not yet in the users list. + //We'll just assume the ones not in the users' list are newer and therefore better and add + //them at the top + NSUInteger shift = 0; + for (NSString *validCipher in supportedCiphers) { + if(![sslCiphers containsObject:validCipher]) { + [sslCiphers insertObject:validCipher atIndex:shift++]; + } + } + } + else { + //no user prefs configured, so we'll just go with the defaults + [sslCiphers addObjectsFromArray:supportedCiphers]; + } + + //reload UI + [sslCipherView deselectAll:nil]; + [sslCipherView reloadData]; +} + +- (void)storeSSLCiphers +{ + NSString *flattedList = [sslCiphers componentsJoinedByString:@":"]; + [prefs setObject:flattedList forKey:SPSSLCipherListKey]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [sslCiphers count]; +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +{ + NSString *value = [sslCiphers objectAtIndex:rowIndex]; + if ([value isEqualTo:SPSSLCipherListMarkerItem]) { + return NSLocalizedString(@"Disabled Cipher Suites", @"Preferences : Network : SSL Chiper suites : List seperator"); + } + return value; +} + +- (BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row +{ + return ([[sslCiphers objectAtIndex:row] isEqualTo:SPSSLCipherListMarkerItem]); +} + +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row +{ + return ![self tableView:tableView isGroupRow:row]; +} + +- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation +{ + if(row < 0) return NO; //why is that even a signed int when all "indexes" are unsigned!? + + NSPasteboard *pboard = [info draggingPasteboard]; + NSArray *draggedItems = [NSKeyedUnarchiver unarchiveObjectWithData:[pboard dataForType:SPSSLCipherPboardTypeName]]; + + NSUInteger nextInsert = row; + for (NSString *item in draggedItems) { + NSUInteger oldPos = [sslCiphers indexOfObject:item]; + [sslCiphers removeObjectAtIndex:oldPos]; + + if(oldPos < (NSUInteger)row) { + // readjust position because we removed an object further up in the list, shifting all following indexes down by 1 + nextInsert--; + } + + [sslCiphers insertObject:item atIndex:nextInsert++]; + } + + NSMutableIndexSet *newSelection = [NSMutableIndexSet indexSet]; + for (NSString *item in draggedItems) { + [newSelection addIndex:[sslCiphers indexOfObject:item]]; + } + + [self storeSSLCiphers]; + [sslCipherView selectRowIndexes:newSelection byExtendingSelection:NO]; + + return YES; +} + +- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation +{ + //cannot drop something on another item in the list, only between them + return (operation == NSTableViewDropOn)? NSDragOperationNone : NSDragOperationMove; +} + +- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard +{ + //the marker cannot be actively reordered + if ([rowIndexes containsIndex:[sslCiphers indexOfObject:SPSSLCipherListMarkerItem]]) + return NO; + + //put the names of the items on the pasteboard. easier to work with than indexes... + NSMutableArray *items = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; + [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [items addObject:[sslCiphers objectAtIndex:idx]]; + }]; + + NSData *arch = [NSKeyedArchiver archivedDataWithRootObject:items]; + [pboard declareTypes:@[SPSSLCipherPboardTypeName] owner:self]; + [pboard setData:arch forType:SPSSLCipherPboardTypeName]; + return YES; +} + +- (IBAction)resetCipherList:(id)sender +{ + //remove the user pref and reset the GUI + [prefs removeObjectForKey:SPSSLCipherListKey]; + [self loadSSLCiphers]; +} + ++ (NSArray *)defaultSSLCipherList +{ + //this is the default list as hardcoded in SPMySQLConnection.m + //Sadly there is no way to make MySQL give us the list of runtime-supported ciphers. + return @[@"DHE-RSA-AES256-SHA", + @"AES256-SHA", + @"DHE-RSA-AES128-SHA", + @"AES128-SHA", + @"AES256-RMD", + @"AES128-RMD", + @"DES-CBC3-RMD", + @"DHE-RSA-AES256-RMD", + @"DHE-RSA-AES128-RMD", + @"DHE-RSA-DES-CBC3-RMD", + @"RC4-SHA", + @"RC4-MD5", + @"DES-CBC3-SHA", + @"DES-CBC-SHA", + @"EDH-RSA-DES-CBC3-SHA", + @"EDH-RSA-DES-CBC-SHA", + SPSSLCipherListMarkerItem, //marker. disabled items below here + @"EDH-DSS-DES-CBC-SHA", + @"EDH-DSS-DES-CBC3-SHA", + @"DHE-DSS-AES128-SHA", + @"DHE-DSS-AES256-SHA", + @"DHE-DSS-DES-CBC3-RMD", + @"DHE-DSS-AES128-RMD", + @"DHE-DSS-AES256-RMD"]; +} + @end -- cgit v1.2.3