Add setup method to UD manager. Try to verify server certificate expiration.

This commit is contained in:
Matthew Chen 2018-10-01 11:38:40 -04:00
parent 7fd15d2fd9
commit f7379deb69
6 changed files with 209 additions and 19 deletions

View File

@ -44,6 +44,7 @@
#import <SignalServiceKit/OWSPrimaryStorage+Calling.h>
#import <SignalServiceKit/OWSReadReceiptManager.h>
#import <SignalServiceKit/SSKEnvironment.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSDatabaseView.h>
#import <SignalServiceKit/TSPreKeyManager.h>
@ -1087,6 +1088,8 @@ static NSTimeInterval launchStartedAt;
// Resume lazy restore.
[OWSBackupLazyRestoreJob runAsync];
#endif
[SSKEnvironment.shared.udManager setup];
}
- (void)registrationStateDidChange

View File

@ -0,0 +1,13 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface OWSCertificateExpiration : NSObject
+ (nullable NSDate *)expirationDataForCertificate:(NSData *)certificateData;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,109 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSCertificateExpiration.h"
#import "OWSFileSystem.h"
#import <CommonCrypto/CommonCrypto.h>
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSData+OWS.h>
#import <openssl/x509.h>
NS_ASSUME_NONNULL_BEGIN
@implementation OWSCertificateExpiration
// PEM is just a series of blocks of base-64 encoded DER data.
//
// https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
+ (nullable NSArray<NSData *> *)convertPemToDer:(NSString *)pemString
{
NSMutableArray<NSData *> *certificateDatas = [NSMutableArray new];
NSError *error;
// We use ? for non-greedy matching.
NSRegularExpression *_Nullable regex = [NSRegularExpression
regularExpressionWithPattern:@"-----BEGIN.*?-----(.+?)-----END.*?-----"
options:NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators
error:&error];
if (!regex || error) {
OWSFailDebug(@"could parse regex: %@.", error);
return nil;
}
[regex enumerateMatchesInString:pemString
options:0
range:NSMakeRange(0, pemString.length)
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *stop) {
if (result.numberOfRanges != 2) {
OWSFailDebug(@"invalid PEM regex match.");
return;
}
NSString *_Nullable derString = [pemString substringWithRange:[result rangeAtIndex:1]];
if (derString.length < 1) {
OWSFailDebug(@"empty PEM match.");
return;
}
// dataFromBase64String will ignore whitespace, which is
// necessary.
NSData *_Nullable derData = [NSData dataFromBase64String:derString];
if (derData.length < 1) {
OWSFailDebug(@"could not parse PEM match.");
return;
}
[certificateDatas addObject:derData];
}];
return certificateDatas;
}
+ (nullable NSDate *)expirationDataForCertificate:(NSData *)certificateData
{
OWSAssertDebug(certificateData);
NSString *temporaryFilePath = [OWSFileSystem temporaryFilePath];
[certificateData writeToFile:temporaryFilePath atomically:YES];
OWSLogInfo(@"temporaryFilePath: %@", temporaryFilePath);
OWSLogInfo(@"certificateData: %@", certificateData.hexadecimalString);
NSString *pemString = [[NSString alloc] initWithData:certificateData encoding:NSUTF8StringEncoding];
OWSLogInfo(@"pemString: %@", pemString);
[DDLog flushLog];
if (certificateData.length >= UINT32_MAX) {
OWSFailDebug(@"certificate data is too long.");
return nil;
}
const unsigned char *certificateDataBytes = (const unsigned char *)[certificateData bytes];
X509 *_Nullable certificateX509 = d2i_X509(NULL, &certificateDataBytes, [certificateData length]);
if (!certificateX509) {
OWSFailDebug(@"could not parse certificate.");
return nil;
}
ASN1_TIME *not_after = X509_get_notAfter(certificateX509);
OWSAssert(not_after);
BIO *b = BIO_new(BIO_s_mem());
int rc = ASN1_TIME_print(b, not_after);
if (rc <= 0) {
OWSLogError(@"ASN1_TIME_print() failed.");
BIO_free(b);
return nil;
}
const NSUInteger kASN1TimeBufferLength = 128;
char buffer[kASN1TimeBufferLength];
rc = BIO_gets(b, buffer, kASN1TimeBufferLength);
if (rc <= 0) {
OWSLogError(@"BIO_gets() failed.");
BIO_free(b);
return nil;
}
BIO_free(b);
return nil;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -11,6 +11,10 @@ public enum OWSUDError: Error {
@objc public protocol OWSUDManager: class {
@objc func setup()
// MARK: - Recipient state
@objc func isUDRecipientId(_ recipientId: String) -> Bool
// No-op if this recipient id is already marked as a "UD recipient".
@ -18,6 +22,9 @@ public enum OWSUDError: Error {
// No-op if this recipient id is already marked as _NOT_ a "UD recipient".
@objc func removeUDRecipientId(_ recipientId: String)
@objc func ensureServerCertificateObjC(success:@escaping (Data) -> Void,
failure:@escaping (Error) -> Void)
}
// MARK: -
@ -40,6 +47,26 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
SwiftSingletons.register(self)
}
@objc public func setup() {
AppReadiness.runNowOrWhenAppIsReady {
guard TSAccountManager.isRegistered() else {
return
}
self.ensureServerCertificate().retainUntilComplete()
}
NotificationCenter.default.addObserver(self,
selector: #selector(registrationStateDidChange),
name: .RegistrationStateDidChange,
object: nil)
}
@objc
func registrationStateDidChange() {
AssertIsOnMainThread()
ensureServerCertificate().retainUntilComplete()
}
// MARK: - Singletons
private var networkManager: TSNetworkManager {
@ -73,10 +100,17 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
#endif
private func serverCertificate() -> Data? {
return nil
guard let certificateData = dbConnection.object(forKey: kUDCurrentServerCertificateKey, inCollection: kUDCollection) as? Data else {
return nil
}
// TODO: Parse certificate and ensure that it is still valid.
// Parse certificate and ensure that it is still valid.
guard !isCertificateExpired(certificateData: certificateData) else {
Logger.warn("Current server certificate has expired.")
return nil
}
return certificateData
}
@ -98,7 +132,7 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
public func ensureServerCertificate() -> Promise<Data> {
return Promise { fulfill, reject in
// If there is an existing server certificate, use that.
// If there is a valid cached server certificate, use that.
if let certificateData = serverCertificate() {
fulfill(certificateData)
return
@ -123,9 +157,16 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
do {
let certificateData = try self.parseServerCertificateResponse(responseObject: responseObject)
guard !self.isCertificateExpired(certificateData: certificateData) else {
reject (OWSUDError.assertionError(description: "Invalid server certificate returned by server"))
return
}
// Cache the current server certificate.
self.setServerCertificate(certificateData)
fulfill(certificateData)
} catch {
reject(error)
}
},
@ -147,4 +188,12 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
return try parser.requiredBase64EncodedData(key: "certificate")
}
private func isCertificateExpired(certificateData: Data) -> Bool {
guard let expirationData = OWSCertificateExpiration.expirationData(forCertificate: certificateData) else {
return true
}
// TODO:
return false
}
}

View File

@ -26,24 +26,24 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) {
//#ifndef DEBUG
// Production
#define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/"
#define textSecureServerURL @"https://textsecure-service.whispersystems.org/"
#define textSecureCDNServerURL @"https://cdn.signal.org"
// Use same reflector for service and CDN
#define textSecureServiceReflectorHost @"textsecure-service-reflected.whispersystems.org"
#define textSecureCDNReflectorHost @"textsecure-service-reflected.whispersystems.org"
#define contactDiscoveryURL @"https://api.directory.signal.org"
//// Production
//#define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/"
//#define textSecureServerURL @"https://textsecure-service.whispersystems.org/"
//#define textSecureCDNServerURL @"https://cdn.signal.org"
//// Use same reflector for service and CDN
//#define textSecureServiceReflectorHost @"textsecure-service-reflected.whispersystems.org"
//#define textSecureCDNReflectorHost @"textsecure-service-reflected.whispersystems.org"
//#define contactDiscoveryURL @"https://api.directory.signal.org"
//#else
//
//// Staging
//#define textSecureWebSocketAPI @"wss://textsecure-service-staging.whispersystems.org/v1/websocket/"
//#define textSecureServerURL @"https://textsecure-service-staging.whispersystems.org/"
//#define textSecureCDNServerURL @"https://cdn-staging.signal.org"
//#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com";
//#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com";
//#define contactDiscoveryURL @"https://api-staging.directory.signal.org"
#define textSecureWebSocketAPI @"wss://textsecure-service-staging.whispersystems.org/v1/websocket/"
#define textSecureServerURL @"https://textsecure-service-staging.whispersystems.org/"
#define textSecureCDNServerURL @"https://cdn-staging.signal.org"
#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com";
#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com";
#define contactDiscoveryURL @"https://api-staging.directory.signal.org"
//
//#endif

View File

@ -9,9 +9,11 @@ import Foundation
@objc
public class OWSFakeUDManager: NSObject, OWSUDManager {
private var udRecipientSet = Set<String>()
@objc public func setup() {}
// MARK: -
// MARK: - Recipient state
private var udRecipientSet = Set<String>()
@objc
public func isUDRecipientId(_ recipientId: String) -> Bool {
@ -27,6 +29,20 @@ public class OWSFakeUDManager: NSObject, OWSUDManager {
public func removeUDRecipientId(_ recipientId: String) {
udRecipientSet.remove(recipientId)
}
// MARK: - Server Certificate
// Tests can control the behavior of this mock by setting this property.
@objc public var nextServerCertificate: Data?
@objc public func ensureServerCertificateObjC(success:@escaping (Data) -> Void,
failure:@escaping (Error) -> Void) {
guard let certificateData = nextServerCertificate else {
failure(OWSUDError.assertionError(description: "No mock server certificate data"))
return
}
success(certificateData)
}
}
#endif