diff --git a/ChatSecure.xcodeproj/project.pbxproj b/ChatSecure.xcodeproj/project.pbxproj index 371c1e548..4a1d6f7bf 100644 --- a/ChatSecure.xcodeproj/project.pbxproj +++ b/ChatSecure.xcodeproj/project.pbxproj @@ -476,6 +476,8 @@ D9A429D01F31169F00BD2545 /* UIAlertController+ChatSecure.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9A429CF1F31169F00BD2545 /* UIAlertController+ChatSecure.swift */; }; D9A7756F1E43F8A200027864 /* ProxyXMPPStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D9A7756D1E43F8A200027864 /* ProxyXMPPStream.h */; }; D9A775701E43F8A200027864 /* ProxyXMPPStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D9A7756E1E43F8A200027864 /* ProxyXMPPStream.m */; }; + D9A7BCE71E4554E200888A8E /* OTRXMPPStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D9A7BCE51E4554E200888A8E /* OTRXMPPStream.h */; }; + D9A7BCE81E4554E200888A8E /* OTRXMPPStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D9A7BCE61E4554E200888A8E /* OTRXMPPStream.m */; }; D9ABD71E1ED787EE00219A9C /* OTRHTMLItem.h in Headers */ = {isa = PBXBuildFile; fileRef = D9ABD71C1ED787EE00219A9C /* OTRHTMLItem.h */; }; D9ABD71F1ED787EE00219A9C /* OTRHTMLItem.m in Sources */ = {isa = PBXBuildFile; fileRef = D9ABD71D1ED787EE00219A9C /* OTRHTMLItem.m */; }; D9ABD72B1ED7886100219A9C /* OTRTextItem.h in Headers */ = {isa = PBXBuildFile; fileRef = D9ABD7291ED7886100219A9C /* OTRTextItem.h */; }; @@ -1116,6 +1118,8 @@ D9A429CF1F31169F00BD2545 /* UIAlertController+ChatSecure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+ChatSecure.swift"; sourceTree = ""; }; D9A7756D1E43F8A200027864 /* ProxyXMPPStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProxyXMPPStream.h; sourceTree = ""; }; D9A7756E1E43F8A200027864 /* ProxyXMPPStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProxyXMPPStream.m; sourceTree = ""; }; + D9A7BCE51E4554E200888A8E /* OTRXMPPStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OTRXMPPStream.h; sourceTree = ""; }; + D9A7BCE61E4554E200888A8E /* OTRXMPPStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OTRXMPPStream.m; sourceTree = ""; }; D9ABD71C1ED787EE00219A9C /* OTRHTMLItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OTRHTMLItem.h; sourceTree = ""; }; D9ABD71D1ED787EE00219A9C /* OTRHTMLItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OTRHTMLItem.m; sourceTree = ""; }; D9ABD7291ED7886100219A9C /* OTRTextItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OTRTextItem.h; sourceTree = ""; }; @@ -1418,6 +1422,8 @@ D9F8C3C11FBFD2CA00D4B857 /* RoomManager.swift */, D98B8E301E4CF90400A713E1 /* OTRServerCapabilities.h */, D98B8E311E4CF90400A713E1 /* OTRServerCapabilities.m */, + D9A7BCE51E4554E200888A8E /* OTRXMPPStream.h */, + D9A7BCE61E4554E200888A8E /* OTRXMPPStream.m */, D9A7756D1E43F8A200027864 /* ProxyXMPPStream.h */, D9A7756E1E43F8A200027864 /* ProxyXMPPStream.m */, 63D639E11D12124F002B4175 /* OTRStreamManagementDelegate.swift */, @@ -2205,6 +2211,7 @@ D93DDB961BA79A9800CD8331 /* OTRChatDemo.h in Headers */, D93DDB2E1BA79A7000CD8331 /* UIActivity+ChatSecure.h in Headers */, D9BEF8E01DCE6E12009945D1 /* OTRXMPPManager_Private.h in Headers */, + D9A7BCE71E4554E200888A8E /* OTRXMPPStream.h in Headers */, D93DDB4B1BA79A7300CD8331 /* OTRXMPPTorManager.h in Headers */, D93DDB891BA79A8C00CD8331 /* OTRXMPPTorAccount.h in Headers */, D93DDBA81BA79AAA00CD8331 /* OTRXMPPServerListViewController.h in Headers */, @@ -3070,6 +3077,7 @@ D943AA421E6A0BA3007F3564 /* XMPPAccountCell.swift in Sources */, D93DDA911BA79A2400CD8331 /* OTRStreamManagementYapStorage.m in Sources */, D93DDA941BA79A2400CD8331 /* OTRXMPPBuddyTimers.m in Sources */, + D9A7BCE81E4554E200888A8E /* OTRXMPPStream.m in Sources */, D9AE3A331BA8D9AB00255537 /* OTRConstants.m in Sources */, D926C19B20FBDD9E0053C538 /* Compatibility.swift in Sources */, 63363FAE1CCAE29B00B0C720 /* OTRYapExtensions.swift in Sources */, diff --git a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.m b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.m index 93f57d33c..ff0430bfc 100644 --- a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.m +++ b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.m @@ -107,8 +107,8 @@ - (YapDatabaseConnection*) databaseConnection { #pragma mark Private //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (XMPPStream*) newStream { - return [[XMPPStream alloc] init]; +- (OTRXMPPStream*) newStream { + return [[OTRXMPPStream alloc] init]; } - (void)setupStream @@ -472,6 +472,8 @@ - (BOOL)startConnection } [self.xmppStream setHostPort:self.account.port]; + + [self.xmppStream setCertificatePinning:self.account.certificatePinning]; error = nil; diff --git a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager_Private.h b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager_Private.h index 0591ecdc2..cd955c9ac 100644 --- a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager_Private.h +++ b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager_Private.h @@ -12,12 +12,12 @@ #import #import "OTRXMPPRoomManager.h" #import "OTRXMPPBuddyTimers.h" -@import XMPPFramework; +#import "OTRXMPPStream.h" NS_ASSUME_NONNULL_BEGIN @interface OTRXMPPManager() -@property (nonatomic, strong, readonly) XMPPStream *xmppStream; +@property (nonatomic, strong, readonly) OTRXMPPStream *xmppStream; @property (nonatomic, strong, readonly) XMPPReconnect *xmppReconnect; @property (nonatomic, strong, readonly) XMPPvCardTempModule *xmppvCardTempModule; @property (nonatomic, strong, readonly) XMPPvCardAvatarModule *xmppvCardAvatarModule; @@ -56,8 +56,8 @@ NS_ASSUME_NONNULL_BEGIN /** wtf. why isn't this being picked up by OTRProtocol */ - (void) connectUserInitiated:(BOOL)userInitiated; -/** Return a newly allocated stream object. This is overridden in OTRXMPPTorManager to use ProxyXMPPStream instead of XMPPStream */ -- (XMPPStream*) newStream; +/** Return a newly allocated stream object. This is overridden in OTRXMPPTorManager to use ProxyXMPPStream instead of OTRXMPPStream */ +- (OTRXMPPStream*) newStream; @end NS_ASSUME_NONNULL_END diff --git a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPStream.h b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPStream.h new file mode 100644 index 000000000..80effedab --- /dev/null +++ b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPStream.h @@ -0,0 +1,18 @@ +// +// OTRXMPPStream.h +// ChatSecure +// +// Created by Chris Ballinger on 2/3/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +@import Foundation; +@import XMPPFramework; + +@interface OTRXMPPStream : XMPPStream + +@property (nonatomic, readonly) BOOL certificatePinning; + +- (void)setCertificatePinning:(BOOL)certificatePinning; + +@end diff --git a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPStream.m b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPStream.m new file mode 100644 index 000000000..d331cfc07 --- /dev/null +++ b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPStream.m @@ -0,0 +1,18 @@ +// +// OTRXMPPStream.m +// ChatSecure +// +// Created by Chris Ballinger on 2/3/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "OTRXMPPStream.h" + +@implementation OTRXMPPStream + +- (void)setCertificatePinning:(BOOL)certificatePinning +{ + _certificatePinning = certificatePinning; +} + +@end diff --git a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPTorManager.m b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPTorManager.m index 1952c4d04..cfd54fec3 100644 --- a/ChatSecure/Classes/Controllers/XMPP/OTRXMPPTorManager.m +++ b/ChatSecure/Classes/Controllers/XMPP/OTRXMPPTorManager.m @@ -30,7 +30,7 @@ - (void) connectUserInitiated:(BOOL)userInitiated { } /** Override XMPPStream with XMPPProxyStream */ -- (XMPPStream*) newStream { +- (OTRXMPPStream*) newStream { return [[ProxyXMPPStream alloc] init]; } diff --git a/ChatSecure/Classes/Controllers/XMPP/ProxyXMPPStream.h b/ChatSecure/Classes/Controllers/XMPP/ProxyXMPPStream.h index e6b97a476..9a9ebf270 100644 --- a/ChatSecure/Classes/Controllers/XMPP/ProxyXMPPStream.h +++ b/ChatSecure/Classes/Controllers/XMPP/ProxyXMPPStream.h @@ -6,10 +6,10 @@ // Copyright © 2017 Chris Ballinger. All rights reserved. // -@import XMPPFramework; +#import "OTRXMPPStream.h" @import ProxyKit; -@interface ProxyXMPPStream : XMPPStream +@interface ProxyXMPPStream : OTRXMPPStream /** * Sets SOCKS proxy host and port diff --git a/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRAccount.h b/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRAccount.h index f8930f8ba..230d9932a 100644 --- a/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRAccount.h +++ b/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRAccount.h @@ -46,6 +46,8 @@ extern NSString *const OTRXMPPTorImageName; /** Whether or not user would like to auto fetch media messages */ @property (nonatomic, readwrite) BOOL disableAutomaticURLFetching; +@property (nonatomic, readwrite) BOOL certificatePinning; + /** * Setting this value does a comparison of against the previously value * to invalidate the OTRImages cache. diff --git a/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRXMPPAccount.m b/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRXMPPAccount.m index 72cba1470..63634c338 100644 --- a/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRXMPPAccount.m +++ b/ChatSecure/Classes/Model/Yap Storage/Accounts/OTRXMPPAccount.m @@ -28,6 +28,7 @@ - (instancetype) initWithUsername:(NSString *)username accountType:(OTRAccountTy _resource = [[self class] newResource]; self.autologin = YES; self.rememberPassword = YES; + self.certificatePinning = NO; } return self; } diff --git a/ChatSecure/Classes/Utilities/OTRCertificatePinning.m b/ChatSecure/Classes/Utilities/OTRCertificatePinning.m index 460f058d6..7c8826885 100644 --- a/ChatSecure/Classes/Utilities/OTRCertificatePinning.m +++ b/ChatSecure/Classes/Utilities/OTRCertificatePinning.m @@ -17,6 +17,7 @@ #import "OTRConstants.h" #import "OTRLog.h" +#import "OTRXMPPStream.h" /////////////////////////////////////////////// @@ -296,15 +297,19 @@ + (id)publicKeyWithCertData:(NSData *)certData **/ #pragma - mark GCDAsyncSockeTDelegate Methods -- (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL))completionHandler +- (void)xmppStream:(OTRXMPPStream *)sender didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL))completionHandler { + BOOL certificatePinning = sender.certificatePinning; NSString *hostName = sender.myJID.domain; // We should have a hostName. If we don't, something is wrong. NSParameterAssert(hostName.length > 0); if (!hostName.length) { completionHandler(NO); } - BOOL trusted = [self isValidPinnedTrust:trust withHostName:hostName]; + BOOL trusted = NO; + if (certificatePinning) { + trusted = [self isValidPinnedTrust:trust withHostName:hostName]; + } if (!trusted) { //Delegate firing off for user to verify with status SecTrustResultType result; @@ -312,7 +317,9 @@ - (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust compl SecTrustSetPolicies(trust, policy); OSStatus status = SecTrustEvaluate(trust, &result); CFRelease(policy); - if ([self.delegate respondsToSelector:@selector(newTrust:withHostName:systemTrustResult:)] && status == noErr) { + if (!certificatePinning && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) { + trusted = YES; + } else if ([self.delegate respondsToSelector:@selector(newTrust:withHostName:systemTrustResult:)] && status == noErr) { [self.delegate newTrust:trust withHostName:hostName systemTrustResult:result]; } } diff --git a/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.h b/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.h index 6f55b304f..b0a9e4fdb 100644 --- a/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.h +++ b/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.h @@ -23,6 +23,7 @@ extern NSString *const kOTRXLFormResourceTextFieldTag; extern NSString *const kOTRXLFormXMPPServerTag; extern NSString *const kOTRXLFormUseTorTag; extern NSString *const kOTRXLFormAutomaticURLFetchTag; +extern NSString *const kOTRXLFormCertificatePinningTag; @interface XLFormDescriptor (OTRAccount) diff --git a/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.m b/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.m index cfa250822..f0d198ba1 100644 --- a/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.m +++ b/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXLFormCreator.m @@ -35,6 +35,7 @@ NSString *const kOTRXLFormUseTorTag = @"kOTRXLFormUseTorTag"; NSString *const kOTRXLFormAutomaticURLFetchTag = @"kOTRXLFormAutomaticURLFetchTag"; +NSString *const kOTRXLFormCertificatePinningTag = @"kOTRXLFormCertificatePinningTag"; @implementation XLFormDescriptor (OTRAccount) @@ -59,6 +60,7 @@ + (instancetype) existingAccountFormWithAccount:(OTRAccount *)account torRow.hidden = @YES; } [[descriptor formRowWithTag:kOTRXLFormAutomaticURLFetchTag] setValue:@(!xmppAccount.disableAutomaticURLFetching)]; + [[descriptor formRowWithTag:kOTRXLFormCertificatePinningTag] setValue:@(xmppAccount.certificatePinning)]; } if (account.accountType == OTRAccountTypeXMPPTor) { XLFormRowDescriptor *torRow = [descriptor formRowWithTag:kOTRXLFormUseTorTag]; @@ -135,6 +137,7 @@ + (XLFormDescriptor *)formForAccountType:(OTRAccountType)accountType createAccou otherSection.footerTitle = AUTO_URL_FETCH_WARNING_STRING(); otherSection.hidden = [NSString stringWithFormat:@"$%@==0", kOTRXLFormShowAdvancedTag]; [otherSection addFormRow:[self autoFetchRowDescriptorWithValue:YES]]; + [otherSection addFormRow:[self certificatePinningRowDescriptorWithValue:NO]]; [descriptor addFormSection:basicSection]; [descriptor addFormSection:serverSection]; @@ -167,6 +170,7 @@ + (XLFormDescriptor *)formForAccountType:(OTRAccountType)accountType createAccou [advancedSection addFormRow:[self torRowDescriptorWithValue:NO]]; } [advancedSection addFormRow:[self autoFetchRowDescriptorWithValue:YES]]; + [advancedSection addFormRow:[self certificatePinningRowDescriptorWithValue:NO]]; break; } @@ -179,6 +183,7 @@ + (XLFormDescriptor *)formForAccountType:(OTRAccountType)accountType createAccou [advancedSection addFormRow:[self resourceRowDescriptorWithValue:nil]]; [advancedSection addFormRow:[self autoFetchRowDescriptorWithValue:YES]]; + [advancedSection addFormRow:[self certificatePinningRowDescriptorWithValue:NO]]; break; } @@ -277,6 +282,12 @@ + (XLFormRowDescriptor*) torRowDescriptorWithValue:(BOOL)value { return torRow; } ++ (XLFormRowDescriptor*) certificatePinningRowDescriptorWithValue:(BOOL)value { + XLFormRowDescriptor *certificatePinningRow = [XLFormRowDescriptor formRowDescriptorWithTag:kOTRXLFormCertificatePinningTag rowType:XLFormRowDescriptorTypeBooleanSwitch title:CERTIFICATE_PINNING_STRING()]; + certificatePinningRow.value = @(value); + return certificatePinningRow; +} + + (XLFormRowDescriptor *)resourceRowDescriptorWithValue:(NSString *)value { XLFormRowDescriptor *resourceRowDescriptor = [XLFormRowDescriptor formRowDescriptorWithTag:kOTRXLFormResourceTextFieldTag rowType:XLFormRowDescriptorTypeText title:RESOURCE_STRING()]; diff --git a/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXMPPLoginHandler.m b/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXMPPLoginHandler.m index a881ad885..476179bbf 100644 --- a/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXMPPLoginHandler.m +++ b/ChatSecure/Classes/View Controllers/Login View Controllers/OTRXMPPLoginHandler.m @@ -50,6 +50,9 @@ - (void)moveAccountValues:(OTRXMPPAccount *)account intoForm:(XLFormDescriptor * XLFormRowDescriptor *autofetch = [form formRowWithTag:kOTRXLFormAutomaticURLFetchTag]; autofetch.value = @(!account.disableAutomaticURLFetching); + XLFormRowDescriptor *certificatePinning = [form formRowWithTag:kOTRXLFormCertificatePinningTag]; + certificatePinning.value = @(account.certificatePinning); + [[form formRowWithTag:kOTRXLFormResourceTextFieldTag] setValue:account.resource]; } @@ -145,6 +148,11 @@ - (OTRXMPPAccount *)moveValues:(XLFormDescriptor *)form intoAccount:(OTRXMPPAcco account.disableAutomaticURLFetching = !autofetch.boolValue; } + NSNumber *certificatePinning = [[form formRowWithTag:kOTRXLFormCertificatePinningTag] value]; + if (certificatePinning) { + account.certificatePinning = [certificatePinning boolValue]; + } + // Post-process values via XMPPJID for stringprep if (!jidDomain.length) { diff --git a/OTRAssets/Strings/OTRStrings.h b/OTRAssets/Strings/OTRStrings.h index b78b1ced8..e5fa4cd22 100644 --- a/OTRAssets/Strings/OTRStrings.h +++ b/OTRAssets/Strings/OTRStrings.h @@ -68,6 +68,8 @@ FOUNDATION_EXPORT NSString* Basic_Setup_Hint(); FOUNDATION_EXPORT NSString* Best_Available(); /** "Cancel", Cancel an alert window */ FOUNDATION_EXPORT NSString* CANCEL_STRING(); +/** "Certificate Pinning", */ +FOUNDATION_EXPORT NSString* CERTIFICATE_PINNING_STRING(); /** "ChatSecure Push", Title for ChatSecure Push (this probably doesnt need to be translated) */ FOUNDATION_EXPORT NSString* CHATSECURE_PUSH_STRING(); /** "Chats", Title for chats view */ diff --git a/OTRAssets/Strings/OTRStrings.m b/OTRAssets/Strings/OTRStrings.m index 353cca845..6b6440aa2 100644 --- a/OTRAssets/Strings/OTRStrings.m +++ b/OTRAssets/Strings/OTRStrings.m @@ -68,6 +68,8 @@ NSString* Best_Available() { return [OTRLanguageManager translatedString:@"Best Available"]; } /** "Cancel", Cancel an alert window */ NSString* CANCEL_STRING() { return [OTRLanguageManager translatedString:@"Cancel"]; } +/** "Certificate Pinning", */ +NSString* CERTIFICATE_PINNING_STRING() { return [OTRLanguageManager translatedString:@"Certificate Pinning"]; } /** "ChatSecure Push", Title for ChatSecure Push (this probably doesnt need to be translated) */ NSString* CHATSECURE_PUSH_STRING() { return [OTRLanguageManager translatedString:@"ChatSecure Push"]; } /** "Chats", Title for chats view */