diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 9b0776840..a11ebc8f2 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -407,6 +407,8 @@ private class SignalCallData: NSObject { throw CallError.obsoleteCall(description: "Missing peerConnectionClient in \(#function)") } + Logger.info("session description for outgoing call: \(call.identifiersForLogs), sdp: \(sessionDescription.logSafeDescription).") + return peerConnectionClient.setLocalSessionDescription(sessionDescription).then { do { let offerBuilder = SSKProtoCallMessageOffer.SSKProtoCallMessageOfferBuilder(id: call.signalingId, @@ -708,12 +710,12 @@ private class SignalCallData: NSObject { // Find a sessionDescription compatible with my constraints and the remote sessionDescription return peerConnectionClient.negotiateSessionDescription(remoteDescription: offerSessionDescription, constraints: constraints) }.then { (negotiatedSessionDescription: HardenedRTCSessionDescription) in - Logger.debug("\(self.logTag) set the remote description for: \(newCall.identifiersForLogs)") - guard self.call == newCall else { throw CallError.obsoleteCall(description: "negotiateSessionDescription() response for obsolete call") } + Logger.info("session description for incoming call: \(newCall.identifiersForLogs), sdp: \(negotiatedSessionDescription.logSafeDescription).") + do { let answerBuilder = SSKProtoCallMessageAnswer.SSKProtoCallMessageAnswerBuilder(id: newCall.signalingId, sessionDescription: negotiatedSessionDescription.sdp) diff --git a/Signal/src/call/PeerConnectionClient.swift b/Signal/src/call/PeerConnectionClient.swift index 324803d1e..ded4918c7 100644 --- a/Signal/src/call/PeerConnectionClient.swift +++ b/Signal/src/call/PeerConnectionClient.swift @@ -1115,6 +1115,58 @@ class HardenedRTCSessionDescription { return RTCSessionDescription.init(type: rtcSessionDescription.type, sdp: description) } + + var logSafeDescription: String { + #if DEBUG + return sdp + #else + return redactIPV6(sdp: redactIcePwd(sdp: sdp)) + #endif + } + + private func redactIcePwd(sdp: String) -> String { + #if DEBUG + return sdp + #else + var text = sdp + text = text.replacingOccurrences(of: "\r", with: "\n") + text = text.replacingOccurrences(of: "\n\n", with: "\n") + let lines = text.components(separatedBy: "\n") + let filteredLines: [String] = lines.map { line in + guard !line.contains("ice-pwd") else { + return "[ REDACTED ice-pwd ]" + } + return line + } + let filteredText = filteredLines.joined(separator: "\n") + return filteredText + #endif + } + + private func redactIPV6(sdp: String) -> String { + #if DEBUG + return sdp + #else + + // Example values to match: + // + // * 2001:0db8:85a3:0000:0000:8a2e:0370:7334 + // * 2001:db8:85a3::8a2e:370:7334 + // * ::1 + // * :: + // * ::ffff:192.0.2.128 + // + // See: https://en.wikipedia.org/wiki/IPv6_addresshttps://en.wikipedia.org/wiki/IPv6_address + do { + let regex = try NSRegularExpression(pattern: "[\\da-f]*:[\\da-f]*:[\\da-f:\\.]*", + options: .caseInsensitive) + return regex.stringByReplacingMatches(in: sdp, options: [], range: NSRange(location: 0, length: sdp.count), withTemplate: "[ REDACTED_IPV6_ADDRESS ]") + } catch { + owsFail("Could not redact IPv6 addresses.") + return "[Could not redact IPv6 addresses.]" + } + #endif + } } protocol VideoCaptureSettingsDelegate: class { diff --git a/Signal/test/call/PeerConnectionClientTest.swift b/Signal/test/call/PeerConnectionClientTest.swift index a092df808..09fee6e17 100644 --- a/Signal/test/call/PeerConnectionClientTest.swift +++ b/Signal/test/call/PeerConnectionClientTest.swift @@ -130,7 +130,7 @@ class PeerConnectionClientTest: XCTestCase { XCTAssertEqual(1, clientDelegate.dataChannelMessages.count) let dataChannelMessageProto = clientDelegate.dataChannelMessages[0] - XCTAssert(dataChannelMessageProto.hasHangup) + XCTAssertNotNil(dataChannelMessageProto.hangup) let hangupProto = dataChannelMessageProto.hangup! XCTAssertEqual(123, hangupProto.id) diff --git a/Signal/test/util/OWSScrubbingLogFormatterTest.m b/Signal/test/util/OWSScrubbingLogFormatterTest.m index 0921a2b6b..6aa0bf4fb 100644 --- a/Signal/test/util/OWSScrubbingLogFormatterTest.m +++ b/Signal/test/util/OWSScrubbingLogFormatterTest.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSScrubbingLogFormatter.h" @@ -9,22 +9,36 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSScrubbingLogFormatterTest : XCTestCase +@property (nonatomic) NSDate *testDate; + @end @implementation OWSScrubbingLogFormatterTest +- (void)setUp +{ + [super setUp]; + + self.testDate = [NSDate new]; +} + +- (void)tearDown +{ + [super tearDown]; +} + - (DDLogMessage *)messageWithString:(NSString *)string { return [[DDLogMessage alloc] initWithMessage:string level:DDLogLevelInfo flag:0 context:0 - file:nil - function:nil + file:@"mock file name" + function:@"mock function name" line:0 tag:nil options:0 - timestamp:[NSDate new]]; + timestamp:self.testDate]; } - (void)testDataScrubbed @@ -75,7 +89,7 @@ NS_ASSUME_NONNULL_BEGIN } } -- (void)testNonPhonenumberNotScrubbed +- (void)testNonPhoneNumberNotScrubbed { OWSScrubbingLogFormatter *formatter = [OWSScrubbingLogFormatter new]; NSString *actual = @@ -85,6 +99,50 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertNotEqual(NSNotFound, redactedRange.location, "Shouldn't touch non phone string."); } +- (void)testIPAddressesScrubbed +{ + id scrubbingFormatter = [OWSScrubbingLogFormatter new]; + id defaultFormatter = [DDLogFileFormatterDefault new]; + + NSDictionary *valueMap = @{ + @"0.0.0.0" : @"[ REDACTED_IPV4_ADDRESS:...0 ]", + @"127.0.0.1" : @"[ REDACTED_IPV4_ADDRESS:...1 ]", + @"255.255.255.255" : @"[ REDACTED_IPV4_ADDRESS:...255 ]", + @"1.2.3.4" : @"[ REDACTED_IPV4_ADDRESS:...4 ]", + }; + NSArray *messageFormats = @[ + @"a%@b", + @"http://%@", + @"http://%@/", + @"%@ and %@ and %@", + @"%@", + @"%@ %@", + @"no ip address!", + @"", + ]; + + for (NSString *ipAddress in valueMap) { + NSString *redactedIPAddress = valueMap[ipAddress]; + + for (NSString *messageFormat in messageFormats) { + NSString *message = [messageFormat stringByReplacingOccurrencesOfString:@"%@" withString:ipAddress]; + + NSString *unredactedMessage = [defaultFormatter formatLogMessage:[self messageWithString:messageFormat]]; + NSString *expectedRedactedMessage = [defaultFormatter + formatLogMessage:[self messageWithString:[messageFormat + stringByReplacingOccurrencesOfString:@"%@" + withString:redactedIPAddress]]]; + NSString *redactedMessage = [scrubbingFormatter formatLogMessage:[self messageWithString:message]]; + + XCTAssertEqualObjects( + expectedRedactedMessage, redactedMessage, @"Scrubbing failed for message: %@", unredactedMessage); + + NSRange ipAddressRange = [redactedMessage rangeOfString:ipAddress]; + XCTAssertEqual(NSNotFound, ipAddressRange.location, "Failed to redact IP address: %@", unredactedMessage); + } + } +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/utils/OWSScrubbingLogFormatter.m b/SignalMessaging/utils/OWSScrubbingLogFormatter.m index b46e46ff4..c60d14cf0 100644 --- a/SignalMessaging/utils/OWSScrubbingLogFormatter.m +++ b/SignalMessaging/utils/OWSScrubbingLogFormatter.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSScrubbingLogFormatter.h" @@ -8,14 +8,60 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSScrubbingLogFormatter +- (NSRegularExpression *)phoneRegex +{ + static NSRegularExpression *regex = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSError *error; + regex = [NSRegularExpression regularExpressionWithPattern:@"\\+\\d{7,12}(\\d{3})" + options:NSRegularExpressionCaseInsensitive + error:&error]; + if (error || !regex) { + OWSFail(@"%@ could not compile regular expression: %@", self.logTag, error); + } + }); + return regex; +} + +- (NSRegularExpression *)dataRegex +{ + static NSRegularExpression *regex = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSError *error; + regex = [NSRegularExpression regularExpressionWithPattern:@"<([\\da-f]{2})[\\da-f]{6}( [\\da-f]{8})*>" + options:NSRegularExpressionCaseInsensitive + error:&error]; + if (error || !regex) { + OWSFail(@"%@ could not compile regular expression: %@", self.logTag, error); + } + }); + return regex; +} + +- (NSRegularExpression *)ipV4AddressRegex +{ + static NSRegularExpression *regex = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // NOTE: The group matches the last quad of the IPv4 address. + NSError *error; + regex = [NSRegularExpression regularExpressionWithPattern:@"\\d+\\.\\d+\\.\\d+\\.(\\d+)" + options:NSRegularExpressionCaseInsensitive + error:&error]; + if (error || !regex) { + OWSFail(@"%@ could not compile regular expression: %@", self.logTag, error); + } + }); + return regex; +} + - (NSString *__nullable)formatLogMessage:(DDLogMessage *)logMessage { NSString *logString = [super formatLogMessage:logMessage]; - NSRegularExpression *phoneRegex = - [NSRegularExpression regularExpressionWithPattern:@"\\+\\d{7,12}(\\d{3})" - options:NSRegularExpressionCaseInsensitive - error:nil]; + NSRegularExpression *phoneRegex = self.phoneRegex; logString = [phoneRegex stringByReplacingMatchesInString:logString options:0 range:NSMakeRange(0, [logString length]) @@ -25,16 +71,18 @@ NS_ASSUME_NONNULL_BEGIN // We capture only the first two characters of the hex string for logging. // example log line: "Called someFunction with nsData: <01234567 89abcdef>" // scrubbed output: "Called someFunction with nsData: [ REDACTED_DATA:01 ]" - NSRegularExpression *dataRegex = - [NSRegularExpression regularExpressionWithPattern:@"<([\\da-f]{2})[\\da-f]{6}( [\\da-f]{8})*>" - options:NSRegularExpressionCaseInsensitive - error:nil]; - + NSRegularExpression *dataRegex = self.dataRegex; logString = [dataRegex stringByReplacingMatchesInString:logString options:0 range:NSMakeRange(0, [logString length]) withTemplate:@"[ REDACTED_DATA:$1... ]"]; + NSRegularExpression *ipV4AddressRegex = self.ipV4AddressRegex; + logString = [ipV4AddressRegex stringByReplacingMatchesInString:logString + options:0 + range:NSMakeRange(0, [logString length]) + withTemplate:@"[ REDACTED_IPV4_ADDRESS:...$1 ]"]; + return logString; } diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m index e91cd8eff..5be24ae7d 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m @@ -18,28 +18,15 @@ @implementation PhoneNumberUtil -+ (NSObject *)sharedLock -{ - static dispatch_once_t onceToken; - static NSObject *lock = nil; - dispatch_once(&onceToken, ^{ - lock = [NSObject new]; - }); - return lock; -} - + (PhoneNumberUtil *)sharedThreadLocal { - @synchronized(self.sharedLock) - { - NSString *key = PhoneNumberUtil.logTag; - PhoneNumberUtil *_Nullable threadLocal = NSThread.currentThread.threadDictionary[key]; - if (!threadLocal) { - threadLocal = [PhoneNumberUtil new]; - NSThread.currentThread.threadDictionary[key] = threadLocal; - } - return threadLocal; + NSString *key = PhoneNumberUtil.logTag; + PhoneNumberUtil *_Nullable threadLocal = NSThread.currentThread.threadDictionary[key]; + if (!threadLocal) { + threadLocal = [PhoneNumberUtil new]; + NSThread.currentThread.threadDictionary[key] = threadLocal; } + return threadLocal; } - (instancetype)init {