Store certificate in DeviceInfo

This commit is contained in:
Albert Vaca Cintora
2024-06-02 17:33:20 +02:00
parent fbe2df48bf
commit afaa9874ec
10 changed files with 77 additions and 98 deletions

View File

@@ -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
)

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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 {
//

View File

@@ -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,

View File

@@ -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`]
)

View File

@@ -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")