Sketch out sender certificate validation.

pull/1/head
Matthew Chen 7 years ago
parent 45233ec862
commit e98c572158

@ -4,6 +4,8 @@
import Foundation import Foundation
import PromiseKit import PromiseKit
import SignalMetadataKit
import SignalCoreKit
public enum OWSUDError: Error { public enum OWSUDError: Error {
case assertionError(description: String) case assertionError(description: String)
@ -22,9 +24,6 @@ public enum OWSUDError: Error {
// No-op if this recipient id is already marked as _NOT_ a "UD recipient". // No-op if this recipient id is already marked as _NOT_ a "UD recipient".
@objc func removeUDRecipientId(_ recipientId: String) @objc func removeUDRecipientId(_ recipientId: String)
@objc func ensureServerCertificateObjC(success:@escaping (Data) -> Void,
failure:@escaping (Error) -> Void)
} }
// MARK: - // MARK: -
@ -36,7 +35,7 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
private let kUDRecipientModeCollection = "kUDRecipientModeCollection" private let kUDRecipientModeCollection = "kUDRecipientModeCollection"
private let kUDCollection = "kUDCollection" private let kUDCollection = "kUDCollection"
private let kUDCurrentServerCertificateKey = "kUDCurrentServerCertificateKey" private let kUDCurrentSenderCertificateKey = "kUDCurrentSenderCertificateKey"
@objc @objc
public required init(primaryStorage: OWSPrimaryStorage) { public required init(primaryStorage: OWSPrimaryStorage) {
@ -52,7 +51,7 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
guard TSAccountManager.isRegistered() else { guard TSAccountManager.isRegistered() else {
return return
} }
self.ensureServerCertificate().retainUntilComplete() self.ensureSenderCertificate().retainUntilComplete()
} }
NotificationCenter.default.addObserver(self, NotificationCenter.default.addObserver(self,
selector: #selector(registrationStateDidChange), selector: #selector(registrationStateDidChange),
@ -64,7 +63,7 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
func registrationStateDidChange() { func registrationStateDidChange() {
AssertIsOnMainThread() AssertIsOnMainThread()
ensureServerCertificate().retainUntilComplete() ensureSenderCertificate().retainUntilComplete()
} }
// MARK: - Singletons // MARK: - Singletons
@ -90,38 +89,37 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
dbConnection.removeObject(forKey: recipientId, inCollection: kUDRecipientModeCollection) dbConnection.removeObject(forKey: recipientId, inCollection: kUDRecipientModeCollection)
} }
// MARK: - Server Certificate // MARK: - Sender Certificate
#if DEBUG #if DEBUG
@objc @objc
public func hasServerCertificate() -> Bool { public func hasSenderCertificate() -> Bool {
return serverCertificate() != nil return senderCertificate() != nil
} }
#endif #endif
private func serverCertificate() -> Data? { private func senderCertificate() -> Data? {
return nil return nil
guard let certificateData = dbConnection.object(forKey: kUDCurrentServerCertificateKey, inCollection: kUDCollection) as? Data else { guard let certificateData = dbConnection.object(forKey: kUDCurrentSenderCertificateKey, inCollection: kUDCollection) as? Data else {
return nil return nil
} }
// Parse certificate and ensure that it is still valid. guard isValidCertificate(certificateData: certificateData) else {
guard !isCertificateExpired(certificateData: certificateData) else { Logger.warn("Current sender certificate is not valid.")
Logger.warn("Current server certificate has expired.")
return nil return nil
} }
return certificateData return certificateData
} }
private func setServerCertificate(_ certificateData: Data) { private func setSenderCertificate(_ certificateData: Data) {
dbConnection.setObject(certificateData, forKey: kUDCurrentServerCertificateKey, inCollection: kUDCollection) dbConnection.setObject(certificateData, forKey: kUDCurrentSenderCertificateKey, inCollection: kUDCollection)
} }
@objc @objc
public func ensureServerCertificateObjC(success:@escaping (Data) -> Void, public func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
failure:@escaping (Error) -> Void) { failure:@escaping (Error) -> Void) {
ensureServerCertificate() ensureSenderCertificate()
.then(execute: { certificateData in .then(execute: { certificateData in
success(certificateData) success(certificateData)
}) })
@ -130,15 +128,15 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
}).retainUntilComplete() }).retainUntilComplete()
} }
public func ensureServerCertificate() -> Promise<Data> { public func ensureSenderCertificate() -> Promise<Data> {
return Promise { fulfill, reject in return Promise { fulfill, reject in
// If there is a valid cached server certificate, use that. // If there is a valid cached sender certificate, use that.
if let certificateData = serverCertificate() { if let certificateData = senderCertificate() {
fulfill(certificateData) fulfill(certificateData)
return return
} }
// Try to obtain a new server certificate. // Try to obtain a new sender certificate.
requestServerCertificate() requestSenderCertificate()
.then(execute: { certificateData in .then(execute: { certificateData in
fulfill(certificateData) fulfill(certificateData)
}) })
@ -148,22 +146,22 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
} }
} }
private func requestServerCertificate() -> Promise<Data> { private func requestSenderCertificate() -> Promise<Data> {
return Promise { fulfill, reject in return Promise { fulfill, reject in
let request = OWSRequestFactory.udServerCertificateRequest() let request = OWSRequestFactory.udSenderCertificateRequest()
self.networkManager.makeRequest( self.networkManager.makeRequest(
request, request,
success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in
do { do {
let certificateData = try self.parseServerCertificateResponse(responseObject: responseObject) let certificateData = try self.parseSenderCertificateResponse(responseObject: responseObject)
guard !self.isCertificateExpired(certificateData: certificateData) else { guard self.isValidCertificate(certificateData: certificateData) else {
reject (OWSUDError.assertionError(description: "Invalid server certificate returned by server")) reject (OWSUDError.assertionError(description: "Invalid sender certificate returned by server"))
return return
} }
// Cache the current server certificate. // Cache the current sender certificate.
self.setServerCertificate(certificateData) self.setSenderCertificate(certificateData)
fulfill(certificateData) fulfill(certificateData)
} catch { } catch {
@ -181,16 +179,23 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
} }
} }
private func parseServerCertificateResponse(responseObject: Any?) throws -> Data { private func parseSenderCertificateResponse(responseObject: Any?) throws -> Data {
guard let parser = ParamParser(responseObject: responseObject) else { guard let parser = ParamParser(responseObject: responseObject) else {
throw OWSUDError.assertionError(description: "Invalid server certificate response") throw OWSUDError.assertionError(description: "Invalid sender certificate response")
} }
return try parser.requiredBase64EncodedData(key: "certificate") return try parser.requiredBase64EncodedData(key: "certificate")
} }
private func isCertificateExpired(certificateData: Data) -> Bool { private func isValidCertificate(certificateData: Data) -> Bool {
// TODO: do {
let certificate = try SMKSenderCertificate.parse(data: certificateData)
let expirationDate = NSDate.ows_date(withMillisecondsSince1970: certificate.expirationTimestamp)
// TODO: What's the cutoff for rotating your sender cert? A day or two before expiration?
return (expirationDate as NSDate).isAfterNow()
} catch {
OWSLogger.error("Certificate could not be parsed: \(error)")
return false return false
} }
}
} }

@ -93,7 +93,7 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo
#pragma mark - UD #pragma mark - UD
+ (TSRequest *)udServerCertificateRequest; + (TSRequest *)udSenderCertificateRequest;
@end @end

@ -348,7 +348,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - UD #pragma mark - UD
+ (TSRequest *)udServerCertificateRequest + (TSRequest *)udSenderCertificateRequest
{ {
NSString *path = @"/v1/certificate/delivery"; NSString *path = @"/v1/certificate/delivery";
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];

@ -29,20 +29,6 @@ public class OWSFakeUDManager: NSObject, OWSUDManager {
public func removeUDRecipientId(_ recipientId: String) { public func removeUDRecipientId(_ recipientId: String) {
udRecipientSet.remove(recipientId) 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 #endif

Loading…
Cancel
Save