AccSecurityVC.mm 17.4 KB
Newer Older
1
/*
2
 *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
 *
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */
#import "AccSecurityVC.h"

21 22 23 24 25
#import <QUrl>
#import <certificate.h>
#import <tlsmethodmodel.h>
#import <qitemselectionmodel.h>
#import <ciphermodel.h>
26
#import <accountmodel.h>
27 28 29 30 31 32 33 34 35 36 37 38

#import "QNSTreeController.h"
#import "CertificateWC.h"

// Tags for views
#define PVK_PASSWORD_TAG 0
#define OUTGOING_TLS_SRV_NAME 1
#define TLS_NEGOTIATION_TAG 2

#define COLUMNID_NAME   @"CipherNameColumn"
#define COLUMNID_STATE  @"CipherStateColumn"

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
@interface AccSecurityVC () {
    __unsafe_unretained IBOutlet NSOutlineView *cipherListView;
    __unsafe_unretained IBOutlet NSButton *useTLS;
    __unsafe_unretained IBOutlet NSView *tlsContainer;

    __unsafe_unretained IBOutlet NSView *pvkContainer;
    __unsafe_unretained IBOutlet NSImageView *pvkPasswordValidation;

    __unsafe_unretained IBOutlet NSButton *showUserCertButton;
    __unsafe_unretained IBOutlet NSButton *showCAButton;
    __unsafe_unretained IBOutlet NSSecureTextField *pvkPasswordField;
    __unsafe_unretained IBOutlet NSTextField *outgoingTlsServerName;
    __unsafe_unretained IBOutlet NSTextField *tlsNegotiationTimeout;
    __unsafe_unretained IBOutlet NSStepper *tlsNegotiationTimeoutStepper;
    __unsafe_unretained IBOutlet NSPathControl *caListPathControl;
    __unsafe_unretained IBOutlet NSPathControl *certificatePathControl;
    __unsafe_unretained IBOutlet NSPathControl *pvkPathControl;
    __unsafe_unretained IBOutlet NSPopUpButton *tlsMethodList;
    __unsafe_unretained IBOutlet NSButton *srtpRTPFallback;
    __unsafe_unretained IBOutlet NSButton *useSRTP;

    __unsafe_unretained IBOutlet NSButton *verifyCertAsClientButton;
    __unsafe_unretained IBOutlet NSButton *verifyCertAsServerButton;
    __unsafe_unretained IBOutlet NSButton *requireCertButton;
}
64

65
@property QNSTreeController *treeController;
66 67
@property CertificateWC* certificateWC;

68 69 70
@end

@implementation AccSecurityVC
71 72
@synthesize treeController;
@synthesize certificateWC;
73 74 75 76

- (void)awakeFromNib
{
    NSLog(@"INIT Security VC");
77 78 79 80 81
    [pvkPasswordField setTag:PVK_PASSWORD_TAG];
    [outgoingTlsServerName setTag:OUTGOING_TLS_SRV_NAME];
    [tlsNegotiationTimeoutStepper setTag:TLS_NEGOTIATION_TAG];
    [tlsNegotiationTimeout setTag:TLS_NEGOTIATION_TAG];

82
    QObject::connect(AccountModel::instance().selectionModel(),
83 84 85 86 87 88
                     &QItemSelectionModel::currentChanged,
                     [=](const QModelIndex &current, const QModelIndex &previous) {
                         if(!current.isValid())
                             return;
                         [self loadAccount];
                     });
89 90
}

91
- (Account*) currentAccount
92
{
93 94
    auto accIdx = AccountModel::instance().selectionModel()->currentIndex();
    return AccountModel::instance().getAccountByModelIndex(accIdx);
95 96 97 98 99
}

- (void)loadAccount
{
    auto account = [self currentAccount];
100 101 102 103 104

    [self updateControlsWithTag:PVK_PASSWORD_TAG];
    [self updateControlsWithTag:OUTGOING_TLS_SRV_NAME];
    [self updateControlsWithTag:TLS_NEGOTIATION_TAG];

105
    QModelIndex qTlsMethodIdx = account->tlsMethodModel()->selectionModel()->currentIndex();
106 107
    [tlsMethodList removeAllItems];
    [tlsMethodList addItemWithTitle:qTlsMethodIdx.data(Qt::DisplayRole).toString().toNSString()];
108

109
    treeController = [[QNSTreeController alloc] initWithQModel:account->cipherModel()];
110 111 112 113 114 115 116 117
    [treeController setAvoidsEmptySelection:NO];
    [treeController setAlwaysUsesMultipleValuesMarker:YES];
    [treeController setChildrenKeyPath:@"children"];

    [cipherListView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
    [cipherListView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
    [cipherListView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];

118 119
    [useTLS setState:account->isTlsEnabled()];
    [tlsContainer setHidden:!account->isTlsEnabled()];
120

121 122
    [useSRTP setState:account->isSrtpEnabled()];
    [srtpRTPFallback setState:account->isSrtpRtpFallback()];
123 124
    [srtpRTPFallback setEnabled:useSRTP.state];

125 126
    if(account->tlsCaListCertificate() != nil) {
        [caListPathControl setURL:[NSURL fileURLWithPath:account->tlsCaListCertificate()->path().toNSString()]];
127 128 129 130
    } else {
        [caListPathControl setURL:nil];
    }

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
    auto tlsCert = account->tlsCertificate();

    if(tlsCert != nil) {
        [certificatePathControl setURL:[NSURL fileURLWithPath:tlsCert->path().toNSString()]];
        if(tlsCert->requirePrivateKey()) {
            [pvkContainer setHidden:NO];
            if(!account->tlsPrivateKey().isEmpty()) {
                [pvkPathControl setURL:[NSURL fileURLWithPath:account->tlsPrivateKey().toNSString()]];
                if (tlsCert->requirePrivateKeyPassword()) {
                    [pvkPasswordField setHidden:NO];
                } else
                    [pvkPasswordField setHidden:YES];
            } else {
                [pvkPathControl setURL:nil];
            }
        } else {
            [pvkContainer setHidden:YES];
        }
149 150 151 152
    } else {
        [certificatePathControl setURL:nil];
    }

153 154 155 156
    if (account->tlsCaListCertificate())
        [showCAButton setHidden:!(account->tlsCaListCertificate()->isValid() == Certificate::CheckValues::PASSED)];
    else
        [showCAButton setHidden:YES];
157

158 159 160
    [verifyCertAsServerButton setState:account->isTlsVerifyServer()];
    [verifyCertAsClientButton setState:account->isTlsVerifyClient()];
    [requireCertButton setState:account->isTlsRequireClientCertificate()];
161 162 163 164
}

- (IBAction)chooseTlsMethod:(id)sender {
    int index = [sender indexOfSelectedItem];
165 166
    QModelIndex qIdx = [self currentAccount]->tlsMethodModel()->index(index, 0);
    [self currentAccount]->tlsMethodModel()->selectionModel()->setCurrentIndex(qIdx, QItemSelectionModel::ClearAndSelect);
167 168 169
}

- (IBAction)toggleUseTLS:(id)sender {
170
    [self currentAccount]->setTlsEnabled([sender state]);
171 172 173 174
    [tlsContainer setHidden:![sender state]];
}

- (IBAction)toggleUseSRTP:(id)sender {
175
    [self currentAccount]->setSrtpEnabled([sender state]);
176 177 178
    [srtpRTPFallback setEnabled:[sender state]];
}
- (IBAction)toggleRTPFallback:(id)sender {
179
    [self currentAccount]->setSrtpRtpFallback([sender state]);
180 181 182
}

- (IBAction)toggleVerifyCertAsClient:(id)sender {
183
    [self currentAccount]->setTlsVerifyClient([sender state]);
184 185 186
}

- (IBAction)toggleVerifyCertServer:(id)sender {
187
    [self currentAccount]->setTlsVerifyServer([sender state]);
188 189 190
}

- (IBAction)toggleRequireCert:(id)sender {
191
    [self currentAccount]->setTlsRequireClientCertificate([sender state]);
192 193 194 195 196 197
}

- (IBAction)toggleCipher:(id)sender {
    NSInteger row = [sender clickedRow];
    NSTableColumn *col = [sender tableColumnWithIdentifier:COLUMNID_STATE];
    NSButtonCell *cell = [col dataCellForRow:row];
198
    [self currentAccount]->cipherModel()->setData([self currentAccount]->cipherModel()->index(row, 0, QModelIndex()),
199 200 201 202 203 204
                                           cell.state == NSOnState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole);
}

- (void) updateControlsWithTag:(NSInteger) tag
{
    switch (tag) {
205 206 207 208 209 210
        case PVK_PASSWORD_TAG: {
                [pvkPasswordField setStringValue:[self currentAccount]->tlsPassword().toNSString()];
                BOOL passMatch = [self currentAccount]->tlsCertificate() &&
            [self currentAccount]->tlsCertificate()->privateKeyMatch() == Certificate::CheckValues::PASSED;
                [pvkPasswordValidation setImage:[NSImage imageNamed:passMatch?@"ic_action_accept":@"ic_action_cancel"]];
            }
211 212
            break;
        case OUTGOING_TLS_SRV_NAME:
213
            [outgoingTlsServerName setStringValue:[self currentAccount]->tlsServerName().toNSString()];
214 215
            break;
        case TLS_NEGOTIATION_TAG:
216 217
            [tlsNegotiationTimeout setIntegerValue:[self currentAccount]->tlsNegotiationTimeoutSec()];
            [tlsNegotiationTimeoutStepper setIntegerValue:[self currentAccount]->tlsNegotiationTimeoutSec()];
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
            break;
        default:
            break;
    }
}

#pragma mark - NSTextFieldDelegate methods

-(void)controlTextDidChange:(NSNotification *)notif
{
    NSTextField *textField = [notif object];
    NSRange test = [[textField currentEditor] selectedRange];

    [self valueDidChange:textField];
    //FIXME: saving account lose focus because in NSTreeController we remove and reinsert row so View selction change
    [textField.window makeFirstResponder:textField];
    [[textField currentEditor] setSelectedRange:test];
}

- (IBAction) valueDidChange: (id) sender
{
    switch ([sender tag]) {
        case PVK_PASSWORD_TAG:
241
            [self currentAccount]->setTlsPassword([[sender stringValue] UTF8String]);
242 243
            break;
        case OUTGOING_TLS_SRV_NAME:
244
            [self currentAccount]->setTlsServerName([[sender stringValue] UTF8String]);
245 246
            break;
        case TLS_NEGOTIATION_TAG:
247
            [self currentAccount]->setTlsNegotiationTimeoutSec([sender integerValue]);
248 249 250 251 252
            break;
        default:
            break;
    }
    [self updateControlsWithTag:[sender tag]];
253 254 255
}

#pragma mark - NSPathControl delegate methods
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

- (IBAction)caListPathControlSingleClick:(id)sender
{
    NSURL* fileURL;
    if ([sender isKindOfClass:[NSMenuItem class]]) {
        fileURL = nil;
    } else {
        fileURL = [[sender clickedPathComponentCell] URL];
    }
    [self->caListPathControl setURL:fileURL];
    [self currentAccount]->setTlsCaListCertificate([[fileURL path] UTF8String]);

    if ([self currentAccount]->tlsCaListCertificate()->isValid() == Certificate::CheckValues::PASSED) {
        [showCAButton setHidden:NO];
    } else
        [showCAButton setHidden:YES];
272 273
}

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
- (IBAction)certificatePathControlSingleClick:(id)sender
{
    NSURL* fileURL;
    if ([sender isKindOfClass:[NSMenuItem class]]) {
        fileURL = nil;
    } else {
        fileURL = [[sender clickedPathComponentCell] URL];
    }
    [self->certificatePathControl setURL:fileURL];
    [self currentAccount]->setTlsCertificate([[fileURL path] UTF8String]);

    auto cert = [self currentAccount]->tlsCertificate();

    if (cert) {
        [showUserCertButton setHidden:!(cert->isValid() == Certificate::CheckValues::PASSED)];
        [pvkContainer setHidden:!cert->requirePrivateKey()];
    } else {
        [showUserCertButton setHidden:YES];
        [pvkContainer setHidden:YES];
    }

295 296
}

297 298 299 300 301 302 303 304 305 306 307 308 309 310
- (IBAction)pvkFilePathControlSingleClick:(id)sender
{
    NSURL* fileURL;
    if ([sender isKindOfClass:[NSMenuItem class]]) {
        fileURL = nil;
    } else {
        fileURL = [[sender clickedPathComponentCell] URL];
    }
    [self currentAccount]->setTlsPrivateKey([[fileURL path] UTF8String]);
    if([self currentAccount]->tlsCertificate()->requirePrivateKeyPassword()) {
        [pvkPasswordField setHidden:NO];
    } else {
        [pvkPasswordField setHidden:YES];
    }
311 312 313 314 315
}

- (IBAction)showCA:(id)sender
{
    certificateWC = [[CertificateWC alloc] initWithWindowNibName:@"CertificateWindow"];
316
    [certificateWC setCertificate:[self currentAccount]->tlsCaListCertificate()];
317 318 319 320 321 322
    [self.view.window beginSheet:certificateWC.window completionHandler:nil];
}

- (IBAction)showEndpointCertificate:(id)sender
{
    certificateWC = [[CertificateWC alloc] initWithWindowNibName:@"CertificateWindow"];
323
    [certificateWC setCertificate:[self currentAccount]->tlsCertificate()];
324
    [self.view.window beginSheet:certificateWC.window completionHandler:nil];}
325 326 327 328 329 330 331 332 333 334 335

/*
 Delegate method of NSPathControl to determine how the NSOpenPanel will look/behave.
 */
- (void)pathControl:(NSPathControl *)pathControl willDisplayOpenPanel:(NSOpenPanel *)openPanel
{
    NSLog(@"willDisplayOpenPanel");
    [openPanel setAllowsMultipleSelection:NO];
    [openPanel setCanChooseDirectories:NO];
    [openPanel setCanChooseFiles:YES];
    [openPanel setResolvesAliases:YES];
336

337
    if(pathControl == caListPathControl) {
338
        [openPanel setTitle:NSLocalizedString(@"Choose a CA list", @"Open panel title")];
339
    } else if (pathControl == certificatePathControl) {
340 341 342 343 344
        [openPanel setTitle:NSLocalizedString(@"Choose a certificate", @"Open panel title")];
    } else {
        [openPanel setTitle:NSLocalizedString(@"Choose a private key file", @"Open panel title")];
    }

345
    [openPanel setPrompt:NSLocalizedString(@"Choose CA", @"Open panel prompt for 'Choose a file'")];
346 347 348 349 350
    [openPanel setDelegate:self];
}

- (void)pathControl:(NSPathControl *)pathControl willPopUpMenu:(NSMenu *)menu
{
351 352
    NSMenuItem *item;
    if(pathControl == caListPathControl) {
353 354
        item = [menu addItemWithTitle:NSLocalizedString(@"Remove value", @"Contextual menu entry")
                               action:@selector(caListPathControlSingleClick:) keyEquivalent:@""];
355
    } else if (pathControl == certificatePathControl) {
356 357
        item = [menu addItemWithTitle:NSLocalizedString(@"Remove value", @"Contextual menu entry")
                               action:@selector(certificatePathControlSingleClick:) keyEquivalent:@""];
358
    } else {
359 360
        item = [menu addItemWithTitle:NSLocalizedString(@"Remove value", @"Contextual menu entry")
                               action:@selector(pvkFilePathControlSingleClick:) keyEquivalent:@""];
361 362
    }
    [item setTarget:self]; // or whatever target you want
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
}

#pragma mark - NSOpenSavePanelDelegate delegate methods

- (void)panel:(id)sender willExpand:(BOOL)expanding
{
    //NSLog(@"willExpand");
}

- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
{
    //NSLog(@"userEnteredFilename");
}

- (void)panelSelectionDidChange:(id)sender
{
    //NSLog(@"panelSelectionDidChange");
}

- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError
{
    NSLog(@"validateURL");
    return YES;
386 387 388 389 390 391 392 393 394 395
}

#pragma mark - NSMenuDelegate methods

- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
{
    QModelIndex qIdx;

    if([menu.title isEqualToString:@"tlsmethodlist"])
    {
396
        qIdx = [self currentAccount]->tlsMethodModel()->index(index);
397 398 399 400 401 402 403 404
        [item setTitle:qIdx.data(Qt::DisplayRole).toString().toNSString()];
    }
    return YES;
}

- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu
{
    if([menu.title isEqualToString:@"tlsmethodlist"])
405
        return [self currentAccount]->tlsMethodModel()->rowCount();
406 407 408 409 410 411 412 413 414 415 416
}

#pragma mark - NSOutlineViewDelegate methods

// -------------------------------------------------------------------------------
//	shouldSelectItem:item
// -------------------------------------------------------------------------------
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
{
    return YES;
}
417

418 419 420 421 422 423 424
// -------------------------------------------------------------------------------
//	dataCellForTableColumn:tableColumn:item
// -------------------------------------------------------------------------------
- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    NSCell *returnCell = [tableColumn dataCell];
    return returnCell;
425 426
}

427 428 429 430
// -------------------------------------------------------------------------------
//	textShouldEndEditing:fieldEditor
// -------------------------------------------------------------------------------
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor
431
{
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    if ([[fieldEditor string] length] == 0)
    {
        // don't allow empty node names
        return NO;
    }
    else
    {
        return YES;
    }
}

// -------------------------------------------------------------------------------
//	shouldEditTableColumn:tableColumn:item
//
//	Decide to allow the edit of the given outline view "item".
// -------------------------------------------------------------------------------
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    return NO;
}

// -------------------------------------------------------------------------------
//	outlineView:willDisplayCell:forTableColumn:item
// -------------------------------------------------------------------------------
- (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell*)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
    if(!qIdx.isValid())
        return;

    if ([[tableColumn identifier] isEqualToString:COLUMNID_NAME])
    {
        cell.title = qIdx.data(Qt::DisplayRole).toString().toNSString();
    }
}

// -------------------------------------------------------------------------------
//	outlineViewSelectionDidChange:notification
// -------------------------------------------------------------------------------
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
    // ask the tree controller for the current selection
    if([[treeController selectedNodes] count] > 0) {

    }
477 478 479
}

@end