mirror of
https://invent.kde.org/network/kdeconnect-ios.git
synced 2025-12-12 20:36:04 +01:00
Store certificate in DeviceInfo
This commit is contained in:
@@ -74,9 +74,10 @@ enum UIPreview {
|
||||
link: BaseLink(
|
||||
DeviceInfo(
|
||||
id: id.rawValue,
|
||||
protocolVersion: KdeConnectSettings.CurrentProtocolVersion,
|
||||
name: name,
|
||||
type: type,
|
||||
cert: CertificateService.shared.getHostCertificate(),
|
||||
protocolVersion: KdeConnectSettings.CurrentProtocolVersion,
|
||||
incomingCapabilities: NetworkPacket.allPacketTypes,
|
||||
outgoingCapabilities: NetworkPacket.allPacketTypes
|
||||
)
|
||||
|
||||
@@ -309,19 +309,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDeviceIdentityUpdatePacketReceived:(NetworkPacket *)np {
|
||||
NSString *deviceID = [np objectForKey:@"deviceId"];
|
||||
- (void)onDeviceIdentityUpdatePacketReceived:(DeviceInfo *)deviceInfo {
|
||||
NSString *deviceId = [deviceInfo id];
|
||||
os_log_with_type(logger, self.debugLogLevel,
|
||||
"on identity update for %{mask.hash}@ received",
|
||||
deviceID);
|
||||
Device *device = [_devices objectForKey:deviceID];
|
||||
deviceId);
|
||||
Device *device = [_devices objectForKey:deviceId];
|
||||
if (device) {
|
||||
[device updateInfo:[DeviceInfo fromNetworkPacket:np]];
|
||||
[device updateInfo:deviceInfo];
|
||||
[_backgroundServiceDelegate onDevicesListUpdatedWithDevicesListsMap:[self getDevicesLists]];
|
||||
} else {
|
||||
os_log_with_type(logger, OS_LOG_TYPE_FAULT,
|
||||
"missing device %{mask.hash}@ to update for",
|
||||
deviceID);
|
||||
deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,6 @@
|
||||
os_log_with_type(logger, OS_LOG_TYPE_INFO, "bg on device unpair %{mask.hash}@", deviceId);
|
||||
[_settings removeObjectForKey:deviceId];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:_settings forKey:@"savedDevices"];
|
||||
device._SHA256HashFormatted = nil;
|
||||
BOOL status = [[CertificateService shared] deleteRemoteDeviceSavedCertWithDeviceId:deviceId];
|
||||
os_log_with_type(logger, OS_LOG_TYPE_INFO, "Device remove, stored cert also removed with status %d", status);
|
||||
if (_backgroundServiceDelegate) {
|
||||
|
||||
@@ -63,8 +63,6 @@ typedef NS_ENUM(NSUInteger, PairStatus)
|
||||
@property(nonatomic, setter=setPlugins:) NSDictionary<NetworkPacketType, id<Plugin>> *plugins;
|
||||
@property(nonatomic) NSMutableArray* _failedPlugins;
|
||||
|
||||
@property(nonatomic, copy) NSString* _SHA256HashFormatted;
|
||||
|
||||
@property(nonatomic) id<DeviceDelegate> deviceDelegate;
|
||||
|
||||
//@property(readonly,nonatomic) BOOL _testDevice;
|
||||
|
||||
@@ -54,7 +54,6 @@ static const NSTimeInterval kPairingTimeout = 30.0;
|
||||
{
|
||||
_pluginsEnableStatus = [[NSMutableDictionary alloc] initWithDictionary:pluginsEnableStatus];
|
||||
}
|
||||
@synthesize _SHA256HashFormatted;
|
||||
|
||||
// TODO: plugins should be saving their own preferences
|
||||
// Plugin-specific persistent data are stored in the Device object. Plugin objects contain runtime
|
||||
@@ -504,7 +503,6 @@ static const NSTimeInterval kPairingTimeout = 30.0;
|
||||
[coder encodeFloat:_cursorSensitivity forKey:@"_cursorSensitivity"];
|
||||
[coder encodeInteger:_hapticStyle forKey:@"_hapticStyle"];
|
||||
[coder encodeFloat:_pointerSensitivity forKey:@"_pointerSensitivity"];
|
||||
[coder encodeObject:_SHA256HashFormatted forKey:@"_SHA256HashFormatted"];
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
|
||||
@@ -515,19 +513,20 @@ static const NSTimeInterval kPairingTimeout = 30.0;
|
||||
NSInteger protocolVersion = [coder decodeIntegerForKey:@"_protocolVersion"];
|
||||
NSArray<NSString*>* incomingCapabilities = [coder decodeArrayOfObjectsOfClass:[NSString class] forKey:@"_incomingCapabilities"];
|
||||
NSArray<NSString*>* outgoingCapabilities = [coder decodeArrayOfObjectsOfClass:[NSString class] forKey:@"_outgoingCapabilities"];
|
||||
SecCertificateRef cert = [[CertificateService shared] extractSavedCertOfRemoteDeviceWithDeviceId:id];
|
||||
_deviceInfo = [[DeviceInfo alloc] initWithId:id
|
||||
protocolVersion:protocolVersion
|
||||
name:name
|
||||
type:type
|
||||
incomingCapabilities:incomingCapabilities
|
||||
outgoingCapabilities:outgoingCapabilities
|
||||
name:name
|
||||
type:type
|
||||
cert:cert
|
||||
protocolVersion:protocolVersion
|
||||
incomingCapabilities:incomingCapabilities
|
||||
outgoingCapabilities:outgoingCapabilities
|
||||
];
|
||||
_pairStatus = [coder decodeIntegerForKey:@"_pairStatus"];
|
||||
_pluginsEnableStatus = (NSMutableDictionary*)[(NSDictionary*)[coder decodeDictionaryWithKeysOfClass:[NSString class] objectsOfClass:[NSNumber class] forKey:@"_pluginsEnableStatus"] mutableCopy];
|
||||
_cursorSensitivity = [coder decodeFloatForKey:@"_cursorSensitivity"];
|
||||
_hapticStyle = [coder decodeIntegerForKey:@"_hapticStyle"];
|
||||
_pointerSensitivity = [coder decodeFloatForKey:@"_pointerSensitivity"];
|
||||
_SHA256HashFormatted = [coder decodeObjectForKey:@"_SHA256HashFormatted"];
|
||||
|
||||
// To be set later in backgroundServices
|
||||
deviceDelegate = nil;
|
||||
|
||||
@@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol LinkProviderDelegate <NSObject>
|
||||
@optional
|
||||
- (void)onConnectionReceived:(BaseLink *)link;
|
||||
- (void)onDeviceIdentityUpdatePacketReceived:(NetworkPacket *)np;
|
||||
- (void)onDeviceIdentityUpdatePacketReceived:(DeviceInfo *)deviceInfo;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -396,15 +396,11 @@
|
||||
NSString* deviceId=[np objectForKey:@"deviceId"];
|
||||
BaseLink *link = self.connectedLinks[deviceId];
|
||||
|
||||
// If reusing old link, certificate checking is LanLinkProvider's responsibility
|
||||
if (link) {
|
||||
// Last timing to enableBackgroundingOnSocket before stream opens
|
||||
[sock performBlock:^{
|
||||
[sock enableBackgroundingOnSocket];
|
||||
}];
|
||||
sock.userData = deviceId;
|
||||
} else {
|
||||
[sock setDelegate:nil];
|
||||
}
|
||||
|
||||
NSArray *myCerts = [[NSArray alloc] initWithObjects: (__bridge id)_identity, nil];
|
||||
@@ -416,19 +412,8 @@
|
||||
nil];
|
||||
|
||||
os_log_with_type(logger, self.debugLogLevel, "Start Server TLS");
|
||||
[sock startTLS:tlsSettings];
|
||||
|
||||
if (link) {
|
||||
// reuse existing link once socket secures
|
||||
[[self _linkProviderDelegate] onDeviceIdentityUpdatePacketReceived:np];
|
||||
} else {
|
||||
link = [[LanLink alloc] init:sock
|
||||
deviceInfo:[DeviceInfo fromNetworkPacket:np]];
|
||||
self.connectedLinks[deviceId] = link;
|
||||
[[self _linkProviderDelegate] onConnectionReceived:link];
|
||||
}
|
||||
[_pendingSockets removeObject:sock];
|
||||
[_pendingNPs removeObject:np];
|
||||
sock.userData = np;
|
||||
[sock startTLS:tlsSettings]; // Will call didReceiveTrust and then socketDidSecure
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,28 +454,11 @@
|
||||
(id)[NSNumber numberWithInt:1], (id)GCDAsyncSocketManuallyEvaluateTrust,
|
||||
nil];
|
||||
|
||||
[sock startTLS: tlsSettings];
|
||||
os_log_with_type(logger, self.debugLogLevel, "Start Client TLS");
|
||||
|
||||
[sock setDelegate:nil];
|
||||
[_pendingSockets removeObject:sock];
|
||||
|
||||
BaseLink *link = self.connectedLinks[deviceId];
|
||||
if (link) {
|
||||
// reuse existing link once socket secures
|
||||
[[self _linkProviderDelegate] onDeviceIdentityUpdatePacketReceived:np];
|
||||
return;
|
||||
}
|
||||
// create LanLink and inform the background
|
||||
link = [[LanLink alloc] init:sock
|
||||
deviceInfo:[DeviceInfo fromNetworkPacket:np]];
|
||||
self.connectedLinks[deviceId] = link;
|
||||
if ([self _linkProviderDelegate]) {
|
||||
[[self _linkProviderDelegate] onConnectionReceived:link];
|
||||
}
|
||||
sock.userData = np;
|
||||
[sock startTLS: tlsSettings]; // Will call didReceiveTrust and then socketDidSecure
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -575,21 +543,26 @@
|
||||
- (void)socketDidSecure:(GCDAsyncSocket *)sock
|
||||
{
|
||||
os_log_with_type(logger, self.debugLogLevel, "Connection is secure LanLinkProvider");
|
||||
[sock setDelegate:nil];
|
||||
NSUInteger pendingSocketIndex = [_pendingSockets indexOfObject:sock];
|
||||
[_pendingSockets removeObject:sock];
|
||||
|
||||
NetworkPacket* pendingNP = [_pendingNPs objectAtIndex:pendingSocketIndex];
|
||||
NSString *deviceID = [pendingNP objectForKey:@"deviceId"];
|
||||
|
||||
NetworkPacket* np = (NetworkPacket *)sock.userData;
|
||||
NSString *deviceId = [np objectForKey:@"deviceId"];
|
||||
SecCertificateRef cert = [[CertificateService shared] getTempRemoteCertWithDeviceId:deviceId];
|
||||
DeviceInfo* deviceInfo = [DeviceInfo fromNetworkPacket:np cert:cert];
|
||||
|
||||
// if existing LanLink exists, DON'T create a new one
|
||||
LanLink *link = (LanLink *)self.connectedLinks[deviceID];
|
||||
LanLink *link = (LanLink *)self.connectedLinks[deviceId];
|
||||
if (link) {
|
||||
[link setSocket:sock];
|
||||
// reuse existing link once socket secures
|
||||
[[self _linkProviderDelegate] onDeviceIdentityUpdatePacketReceived:deviceInfo];
|
||||
return;
|
||||
} else {
|
||||
// create LanLink and inform the background
|
||||
link = [[LanLink alloc] init:sock
|
||||
deviceInfo:[DeviceInfo fromNetworkPacket:pendingNP]];
|
||||
self.connectedLinks[deviceID] = link;
|
||||
link = [[LanLink alloc] init:sock deviceInfo:deviceInfo];
|
||||
self.connectedLinks[deviceId] = link;
|
||||
if ([self _linkProviderDelegate]) {
|
||||
[[self _linkProviderDelegate] onConnectionReceived:link];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,10 +570,12 @@
|
||||
// it's now our term to evaluate client's certificate.
|
||||
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler
|
||||
{
|
||||
NSString *deviceID = (NSString *)sock.userData;
|
||||
NetworkPacket *np = (NetworkPacket *)sock.userData;
|
||||
NSString* deviceId=[np objectForKey:@"deviceId"];
|
||||
if ([[CertificateService shared] verifyCertificateEqualityWithTrust:trust
|
||||
fromRemoteDeviceWithDeviceID:deviceID]) {
|
||||
fromRemoteDeviceWithDeviceID:deviceId]) {
|
||||
os_log_with_type(logger, OS_LOG_TYPE_INFO, "LanLinkProvider's didReceiveTrust received Certificate from %{mask.hash}@, trusting", [sock connectedHost]);
|
||||
[[CertificateService shared] storeTempRemoteCertFromTrust:trust deviceId:deviceId];
|
||||
completionHandler(YES);// give YES if we want to trust, NO if we don't
|
||||
} else {
|
||||
completionHandler(NO);
|
||||
|
||||
@@ -15,8 +15,6 @@ import CryptoKit
|
||||
@objc let hostIdentity: SecIdentity
|
||||
private let logger = Logger()
|
||||
|
||||
var tempRemoteCerts: [String: SecCertificate] = [:]
|
||||
|
||||
override init() {
|
||||
hostIdentity = Self.loadIdentityFromKeychain()
|
||||
super.init()
|
||||
@@ -51,17 +49,25 @@ import CryptoKit
|
||||
return Self.getCertHash(cert: getHostCertificate())
|
||||
}
|
||||
|
||||
func getRemoteCertificateSHA256HashFormattedString(deviceId: String) -> String {
|
||||
let cert = backgroundService._devices[deviceId]!._deviceInfo.cert
|
||||
return Self.getCertHash(cert: cert)
|
||||
}
|
||||
|
||||
static func getCertHash(cert: SecCertificate) -> String {
|
||||
let certData = SecCertificateCopyData(cert) as Data
|
||||
let certHash = SHA256.hash(data: certData)
|
||||
return SHA256HashDividedAndFormatted(hashDescription: certHash.description)
|
||||
return sha256AsStringWithDividers(hash: certHash)
|
||||
}
|
||||
|
||||
static func sha256AsString(hash: SHA256.Digest) -> String {
|
||||
// hash description looks like: "SHA256 digest: xxxxxxyyyyyyssssssyyyysysss", so the third element of the split separated by " " is just the hash string
|
||||
return (hash.description.components(separatedBy: " "))[2]
|
||||
}
|
||||
|
||||
// Given a standard, no-space SHA256 hash, insert : dividers every 2 characters
|
||||
// It isn't terribly efficient to convert Subtring to String like this but it works?
|
||||
@objc static func SHA256HashDividedAndFormatted(hashDescription: String) -> String {
|
||||
// hashDescription looks like: "SHA256 digest: xxxxxxyyyyyyssssssyyyysysss", so the third element of the split separated by " " is just the hash string
|
||||
var justTheHashString: String = (hashDescription.components(separatedBy: " "))[2]
|
||||
static func sha256AsStringWithDividers(hash: SHA256.Digest) -> String {
|
||||
var justTheHashString = sha256AsString(hash: hash)
|
||||
var arrayOf2CharStrings: [String] = []
|
||||
while (!justTheHashString.isEmpty) {
|
||||
let firstString: String = String(justTheHashString.first!)
|
||||
@@ -91,9 +97,6 @@ import CryptoKit
|
||||
if let storedRemoteCert: SecCertificate = extractSavedCertOfRemoteDevice(deviceId: deviceId) {
|
||||
logger.debug("Both remote cert and stored cert exist, checking them for equality")
|
||||
if ((SecCertificateCopyData(remoteCert) as Data) == (SecCertificateCopyData(storedRemoteCert) as Data)) {
|
||||
// FIXME: This is a weird place to update the device info in backgroundService._devices[
|
||||
backgroundService._devices[deviceId]!._SHA256HashFormatted = Self.getCertHash(cert: remoteCert)
|
||||
tempRemoteCerts[deviceId] = remoteCert
|
||||
return true
|
||||
} else {
|
||||
logger.error("reject remote device for having a different certificate from the stored certificate")
|
||||
@@ -101,7 +104,6 @@ import CryptoKit
|
||||
}
|
||||
} else {
|
||||
logger.debug("remote cert exists, but nothing stored, setting up for new remote device")
|
||||
tempRemoteCerts[deviceId] = remoteCert
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
@@ -141,17 +143,7 @@ import CryptoKit
|
||||
var remoteSavedCert: AnyObject? = nil
|
||||
let status: OSStatus = SecItemCopyMatching(keychainItemQuery, &remoteSavedCert)
|
||||
logger.info("extractSavedCertOfRemoteDevice completed with \(status)")
|
||||
|
||||
if let remoteSavedCert = remoteSavedCert {
|
||||
guard backgroundService._devices.keys.contains(deviceId) else {
|
||||
let deleteStatus = deleteRemoteDeviceSavedCert(deviceId: deviceId)
|
||||
logger.notice("Device object is gone but cert is still here? Removing stored cert with status \(deleteStatus)")
|
||||
return nil
|
||||
}
|
||||
return (remoteSavedCert as! SecCertificate)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return (remoteSavedCert as! SecCertificate?)
|
||||
}
|
||||
|
||||
@objc func saveRemoteDeviceCertToKeychain(cert: SecCertificate, deviceId: String) -> Bool {
|
||||
@@ -185,6 +177,18 @@ import CryptoKit
|
||||
return true
|
||||
}
|
||||
|
||||
// FIXME: the temp remote cert functions are here because I dind't find a way to do this from Objective-C inside LanLink.
|
||||
var tempRemoteCerts: [String: SecCertificate] = [:]
|
||||
|
||||
@objc func storeTempRemoteCert(fromTrust: SecTrust, deviceId: String) {
|
||||
let remoteCert: SecCertificate = extractRemoteCertFromTrust(trust: fromTrust)!
|
||||
tempRemoteCerts[deviceId] = remoteCert
|
||||
}
|
||||
|
||||
@objc func getTempRemoteCert(deviceId: String) -> SecCertificate {
|
||||
return tempRemoteCerts[deviceId]!
|
||||
}
|
||||
|
||||
// Unused and reference functions
|
||||
// @objc static func verifyRemoteCertificate(trust: SecTrust) -> Bool {
|
||||
//
|
||||
|
||||
@@ -44,7 +44,7 @@ extension Notification.Name {
|
||||
}
|
||||
|
||||
@objc func onPairSuccess(_ deviceId: String!) {
|
||||
guard let cert = CertificateService.shared.tempRemoteCerts[deviceId] else {
|
||||
guard let cert = backgroundService.devices[deviceId]?._deviceInfo.cert else {
|
||||
SystemSound.audioError.play()
|
||||
logger.fault("Pairing succeeded without certificate for remote device \(deviceId!, privacy: .private(mask: .hash))")
|
||||
return
|
||||
@@ -52,7 +52,6 @@ extension Notification.Name {
|
||||
|
||||
let status = CertificateService.shared.saveRemoteDeviceCertToKeychain(cert: cert, deviceId: deviceId)
|
||||
logger.info("Remote certificate saved into local Keychain with status \(status)")
|
||||
backgroundService._devices[deviceId]!._SHA256HashFormatted = CertificateService.SHA256HashDividedAndFormatted(hashDescription: SHA256.hash(data: SecCertificateCopyData(CertificateService.shared.tempRemoteCerts[deviceId]!) as Data).description)
|
||||
|
||||
onDevicesListUpdated()
|
||||
NotificationCenter.default.post(name: .pairRequestSucceedNotification, object: nil,
|
||||
|
||||
@@ -19,16 +19,18 @@ public enum DeviceType: Int {
|
||||
class DeviceInfo: NSObject {
|
||||
let id: String
|
||||
let name: String
|
||||
let type: DeviceType // DeviceType2Str
|
||||
let type: DeviceType
|
||||
let cert: SecCertificate
|
||||
let protocolVersion: Int
|
||||
let incomingCapabilities: [NetworkPacket.`Type`]
|
||||
let outgoingCapabilities: [NetworkPacket.`Type`]
|
||||
|
||||
init(id: String, protocolVersion: Int, name: String, type: DeviceType, incomingCapabilities: [NetworkPacket.`Type`], outgoingCapabilities: [NetworkPacket.`Type`]) {
|
||||
init(id: String, name: String, type: DeviceType, cert: SecCertificate, protocolVersion: Int, incomingCapabilities: [NetworkPacket.`Type`], outgoingCapabilities: [NetworkPacket.`Type`]) {
|
||||
self.id = id
|
||||
self.protocolVersion = protocolVersion
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.cert = cert
|
||||
self.protocolVersion = protocolVersion
|
||||
self.incomingCapabilities = incomingCapabilities
|
||||
self.outgoingCapabilities = outgoingCapabilities
|
||||
}
|
||||
@@ -36,21 +38,23 @@ class DeviceInfo: NSObject {
|
||||
static func getOwn() -> DeviceInfo {
|
||||
return DeviceInfo(
|
||||
id: KdeConnectSettings.getUUID(),
|
||||
protocolVersion: KdeConnectSettings.CurrentProtocolVersion,
|
||||
name: KdeConnectSettings.shared.deviceName,
|
||||
type: DeviceType.current,
|
||||
cert: CertificateService.shared.getHostCertificate(),
|
||||
protocolVersion: KdeConnectSettings.CurrentProtocolVersion,
|
||||
// FIXME: actually read what plugins are available
|
||||
incomingCapabilities: KdeConnectSettings.IncomingCapabilities,
|
||||
outgoingCapabilities: KdeConnectSettings.OutgoingCapabilities
|
||||
)
|
||||
}
|
||||
|
||||
static func from(networkPacket: NetworkPacket) -> DeviceInfo {
|
||||
static func from(networkPacket: NetworkPacket, cert: SecCertificate) -> DeviceInfo {
|
||||
return DeviceInfo(
|
||||
id: networkPacket.string(forKey: "deviceId"),
|
||||
protocolVersion: networkPacket.integer(forKey: "protocolVersion"),
|
||||
name: networkPacket.string(forKey: "deviceName"),
|
||||
type: strToDeviceType(str: networkPacket.string(forKey: "deviceType")),
|
||||
cert: cert,
|
||||
protocolVersion: networkPacket.integer(forKey: "protocolVersion"),
|
||||
incomingCapabilities: networkPacket.object(forKey: "outgoingCapabilities") as! [NetworkPacket.`Type`],
|
||||
outgoingCapabilities: networkPacket.object(forKey: "outgoingCapabilities") as! [NetworkPacket.`Type`]
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ struct DevicesDetailView: View {
|
||||
|
||||
Button {
|
||||
alertManager.queueAlert(prioritize: true, title: "Encryption Info") {
|
||||
Text("SHA256 fingerprint of your device certificate is:\n\(CertificateService.shared.getHostCertificateSHA256HashFormattedString())\n\nSHA256 fingerprint of remote device certificate is: \n\((backgroundService._devices[detailsDeviceId]!._SHA256HashFormatted == nil || backgroundService._devices[detailsDeviceId]!._SHA256HashFormatted.isEmpty) ? "Unable to retrieve fingerprint of remote device." : backgroundService._devices[detailsDeviceId]!._SHA256HashFormatted)")
|
||||
Text("SHA256 fingerprint of your device certificate is:\n\(CertificateService.shared.getHostCertificateSHA256HashFormattedString())\n\nSHA256 fingerprint of remote device certificate is: \n\(CertificateService.shared.getRemoteCertificateSHA256HashFormattedString(deviceId: detailsDeviceId))")
|
||||
}
|
||||
} label: {
|
||||
Label("Encryption Info", systemImage: "lock.doc")
|
||||
|
||||
Reference in New Issue
Block a user