mirror of
https://invent.kde.org/network/kdeconnect-ios.git
synced 2025-12-12 20:36:04 +01:00
Sync Backend Certificate Fix for macOS from https://invent.kde.org/ruixuantu/kdeconnect-mac with commits f6dc4b7b and 1533d2da
This commit is contained in:
@@ -16,3 +16,6 @@
|
||||
|
||||
OSStatus generateSecIdentityForUUID(NSString *uuid);
|
||||
NSData* getPublicKeyDERFromCertificate(SecCertificateRef certificate);
|
||||
#if TARGET_OS_OSX
|
||||
NSString* extractSecCertificateDigest(SecCertificateRef certificate);
|
||||
#endif
|
||||
|
||||
@@ -37,8 +37,12 @@ OSStatus generateSecIdentityForUUID(NSString *uuid)
|
||||
"CertificateService");
|
||||
|
||||
// Force to remove the old identity, otherwise the new identity cannot be stored
|
||||
#if !TARGET_OS_OSX
|
||||
NSDictionary *spec = @{(__bridge id)kSecClass: (id)kSecClassIdentity};
|
||||
SecItemDelete((__bridge CFDictionaryRef)spec);
|
||||
#else
|
||||
// Removal for macOS is called in CertificateService.swift before this function call
|
||||
#endif
|
||||
|
||||
|
||||
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
|
||||
@@ -123,9 +127,15 @@ OSStatus generateSecIdentityForUUID(NSString *uuid)
|
||||
|
||||
// Create p12 format data
|
||||
PKCS12 *p12 = NULL;
|
||||
#if !TARGET_OS_OSX
|
||||
p12 = PKCS12_create(/* password */ "", /* name */ "KDE Connect", pkey, x509,
|
||||
/* ca */ NULL, /* nid_key */ 0, /* nid_cert */ 0,
|
||||
/* iter */ 0, /* mac_iter */ PKCS12_DEFAULT_ITER, /* keytype */ 0);
|
||||
#else
|
||||
p12 = PKCS12_create(/* password */ NULL, /* name */ "KDE Connect", pkey, x509,
|
||||
/* ca */ NULL, /* nid_key */ 0, /* nid_cert */ 0,
|
||||
/* iter */ 0, /* mac_iter */ PKCS12_DEFAULT_ITER, /* keytype */ 0);
|
||||
#endif
|
||||
if(!p12) {
|
||||
@throw [[NSException alloc] initWithName:@"Fail getP12File" reason:@"Error creating PKCS#12 structure" userInfo:nil];
|
||||
}
|
||||
@@ -161,6 +171,7 @@ OSStatus generateSecIdentityForUUID(NSString *uuid)
|
||||
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
|
||||
OSStatus securityError = SecPKCS12Import((CFDataRef) p12Data,
|
||||
(CFDictionaryRef)options, &items);
|
||||
#if !TARGET_OS_OSX
|
||||
SecIdentityRef identityApp;
|
||||
if (securityError == noErr && CFArrayGetCount(items) > 0) {
|
||||
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
|
||||
@@ -181,11 +192,16 @@ OSStatus generateSecIdentityForUUID(NSString *uuid)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Delete the temp file
|
||||
[[NSFileManager defaultManager] removeItemAtPath:p12FilePath error:nil];
|
||||
|
||||
#if !TARGET_OS_OSX
|
||||
return noErr;
|
||||
#else
|
||||
return securityError;
|
||||
#endif
|
||||
}
|
||||
|
||||
NSData* getPublicKeyDERFromCertificate(SecCertificateRef certificate) {
|
||||
@@ -221,3 +237,18 @@ NSData* getPublicKeyDERFromCertificate(SecCertificateRef certificate) {
|
||||
|
||||
return spkiData;
|
||||
}
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
NSString* extractSecCertificateDigest(SecCertificateRef certificate) {
|
||||
// https://stackoverflow.com/questions/63350518/how-can-i-translate-seccertificateref-cert-object-to-openssls-x509-certificate
|
||||
// https://stackoverflow.com/questions/8850524/seccertificateref-how-to-get-the-certificate-information
|
||||
// https://stackoverflow.com/questions/15175917/free-string-returned-by-x509-name-oneline
|
||||
NSData *certData = (__bridge NSData *) SecCertificateCopyData(certificate);
|
||||
const unsigned char *certDataBytes = (const unsigned char *)[certData bytes];
|
||||
X509 *certX509 = d2i_X509(NULL, &certDataBytes, [certData length]);
|
||||
char *buffer = calloc(8192, 1);
|
||||
X509_NAME_oneline(X509_get_subject_name(certX509), buffer, 8192);
|
||||
NSString *digest = [NSString stringWithUTF8String:buffer];
|
||||
return digest;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -104,7 +104,10 @@ Keychain API expects as a validly constructed container class.
|
||||
|
||||
[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
|
||||
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
|
||||
#endif
|
||||
|
||||
// The keychain access group attribute determines if this item can be shared
|
||||
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
|
||||
if (accessGroup != nil)
|
||||
@@ -138,6 +141,9 @@ Keychain API expects as a validly constructed container class.
|
||||
|
||||
// Add the generic attribute and the keychain access group.
|
||||
[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
|
||||
#if TARGET_OS_OSX
|
||||
[keychainItemData setObject:@"kdeconnect-uuid" forKey:(id)kSecAttrLabel];
|
||||
#endif
|
||||
if (accessGroup != nil)
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
@@ -246,7 +252,13 @@ Keychain API expects as a validly constructed container class.
|
||||
|
||||
// Acquire the password data from the attributes.
|
||||
NSData *passwordData = NULL;
|
||||
if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef)&passwordData) == noErr)
|
||||
bool status = false;
|
||||
#if TARGET_OS_OSX
|
||||
status = ((passwordData = [returnDictionary objectForKey:(id)kSecValueData]));
|
||||
#else
|
||||
status = SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef)&passwordData) == noErr;
|
||||
#endif
|
||||
if (status)
|
||||
{
|
||||
// Remove the search, class, and identifier key/value, we don't need them anymore.
|
||||
[returnDictionary removeObjectForKey:(id)kSecReturnData];
|
||||
|
||||
@@ -21,6 +21,8 @@ import CryptoKit
|
||||
}
|
||||
|
||||
static func loadIdentityFromKeychain() -> SecIdentity {
|
||||
// ref: https://developer.apple.com/documentation/network/creating_an_identity_for_local_network_tls
|
||||
#if !os(macOS)
|
||||
let keychainItemQuery: CFDictionary = [
|
||||
kSecClass: kSecClassIdentity,
|
||||
kSecAttrLabel: KdeConnectSettings.getUUID() as Any,
|
||||
@@ -29,11 +31,47 @@ import CryptoKit
|
||||
var identityApp: AnyObject? = nil
|
||||
let status: OSStatus = SecItemCopyMatching(keychainItemQuery, &identityApp)
|
||||
Logger().info("getIdentityFromKeychain completed with \(status)")
|
||||
#else
|
||||
var identityApp: SecIdentity? = nil
|
||||
let getquery = [
|
||||
kSecClass: kSecClassCertificate,
|
||||
kSecAttrLabel: KdeConnectSettings.getUUID() as Any,
|
||||
kSecReturnRef: true,
|
||||
kSecMatchLimit: kSecMatchLimitOne,
|
||||
] as NSDictionary
|
||||
var item: CFTypeRef?
|
||||
func macFetchIdentity() {
|
||||
// normally will print error -25300 at the first launch as there is no identity
|
||||
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
|
||||
guard status == errSecSuccess else {
|
||||
print("getHostIdentityFromKeychain status failed with \(status)")
|
||||
return
|
||||
}
|
||||
print("loadIdentityFromKeychain status failed with \(status)")
|
||||
let certificate = item as! SecCertificate
|
||||
let identityStatus = SecIdentityCreateWithCertificate(nil, certificate, &identityApp)
|
||||
guard identityStatus == errSecSuccess else {
|
||||
print("loadIdentityFromKeychain identityStatus failed with \(identityStatus)")
|
||||
return
|
||||
}
|
||||
}
|
||||
macFetchIdentity()
|
||||
#endif
|
||||
if (identityApp == nil) {
|
||||
Logger().info("generateSecIdentity")
|
||||
#if !os(macOS)
|
||||
if generateSecIdentityForUUID(KdeConnectSettings.getUUID()) == noErr {
|
||||
SecItemCopyMatching(keychainItemQuery, &identityApp)
|
||||
}
|
||||
#else
|
||||
// remove old identity on macOS
|
||||
// normally will print error -25300 at the first launch as there is no identity
|
||||
deleteHostCertificateFromKeychain()
|
||||
if (generateSecIdentityForUUID(KdeConnectSettings.getUUID()) == noErr) {
|
||||
// Refetch
|
||||
macFetchIdentity()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return (identityApp as! SecIdentity)
|
||||
}
|
||||
@@ -107,12 +145,34 @@ import CryptoKit
|
||||
}
|
||||
|
||||
// @discardableResult
|
||||
@objc func deleteHostCertificateFromKeychain() -> OSStatus {
|
||||
@objc static func deleteHostCertificateFromKeychain() -> OSStatus {
|
||||
#if !os(macOS)
|
||||
let keychainItemQuery: CFDictionary = [
|
||||
kSecAttrLabel: KdeConnectSettings.getUUID() as Any,
|
||||
kSecClass: kSecClassIdentity,
|
||||
] as CFDictionary
|
||||
return SecItemDelete(keychainItemQuery)
|
||||
#else
|
||||
let deleteCertQuery: NSDictionary = [
|
||||
kSecAttrLabel: KdeConnectSettings.getUUID() as Any,
|
||||
kSecClass: kSecClassCertificate,
|
||||
] as NSDictionary
|
||||
let deleteCertStatus = SecItemDelete(deleteCertQuery)
|
||||
guard deleteCertStatus == errSecSuccess else {
|
||||
print("deleteHostCertificateFromKeychain deleteCert failed with status \(deleteCertStatus)")
|
||||
return deleteCertStatus
|
||||
}
|
||||
let deleteKeyQuery: NSDictionary = [
|
||||
kSecAttrLabel: "KDE Connect" as Any,
|
||||
kSecClass: kSecClassKey,
|
||||
] as NSDictionary
|
||||
let deleteKeyStatus = SecItemDelete(deleteKeyQuery)
|
||||
guard deleteKeyStatus == errSecSuccess else {
|
||||
print("deleteHostCertificateFromKeychain deleteKey failed with status \(deleteKeyStatus)")
|
||||
return deleteKeyStatus
|
||||
}
|
||||
return errSecSuccess
|
||||
#endif
|
||||
}
|
||||
|
||||
// This function is called by LanLink and LanLinkProvider's didReceiveTrust
|
||||
@@ -190,15 +250,61 @@ import CryptoKit
|
||||
}
|
||||
|
||||
@objc func deleteAllItemsFromKeychain() -> Bool {
|
||||
#if !os(macOS)
|
||||
let allSecItemClasses: [CFString] = [kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, kSecClassIdentity]
|
||||
for itemClass in allSecItemClasses {
|
||||
let keychainItemQuery: CFDictionary = [kSecClass: itemClass] as CFDictionary
|
||||
let status: OSStatus = SecItemDelete(keychainItemQuery)
|
||||
if (status != 0) {
|
||||
logger.error("Failed to remove 1 certificate in keychain with error code \(status), continuing to attempt to remove all")
|
||||
if (status != errSecSuccess) {
|
||||
print("Failed to remove 1 certificate in \(itemClass) keychain with error code \(status), continuing to attempt to remove all")
|
||||
}
|
||||
}
|
||||
return true
|
||||
#else
|
||||
print("deleteAllItemsFromKeychain - host cert")
|
||||
let deleteHostCertificateFromKeychainStatus = Self.deleteHostCertificateFromKeychain()
|
||||
if deleteHostCertificateFromKeychainStatus != errSecSuccess {
|
||||
print("deleteAllItemsFromKeychain failed to remove host cert with status \(deleteHostCertificateFromKeychainStatus)")
|
||||
}
|
||||
print("deleteAllItemsFromKeychain - other certs")
|
||||
let getCertsQuery: NSDictionary = [
|
||||
kSecClass: kSecClassCertificate,
|
||||
kSecReturnRef: true,
|
||||
kSecMatchLimit: kSecMatchLimitAll,
|
||||
] as NSDictionary
|
||||
var certs: AnyObject? = nil
|
||||
let getCertsQueryStatus: OSStatus = SecItemCopyMatching(getCertsQuery, &certs)
|
||||
guard getCertsQueryStatus == errSecSuccess else {
|
||||
print("deleteAllItemsFromKeychain getCertsQueryStatus failed with status \(getCertsQueryStatus)")
|
||||
return (getCertsQueryStatus != 0)
|
||||
}
|
||||
let numCerts = (certs as! NSArray).count
|
||||
for certIndex in 0...(numCerts - 1) {
|
||||
let cert: SecCertificate = (certs as! NSArray)[certIndex] as! SecCertificate
|
||||
let digest: String = extractSecCertificateDigest(cert)
|
||||
if (digest.contains("/OU=Kde connect") && digest.contains("/O=KDE")) {
|
||||
print("deleteAllItemsFromKeychain to remove cert with digest \(digest)")
|
||||
let removeCertQuery: NSDictionary = [
|
||||
kSecClass: kSecClassCertificate,
|
||||
kSecValueRef: cert,
|
||||
kSecMatchLimit: kSecMatchLimitOne,
|
||||
] as NSDictionary
|
||||
let removeCertQueryStatus: OSStatus = SecItemDelete(removeCertQuery)
|
||||
if removeCertQueryStatus != errSecSuccess {
|
||||
print("deleteAllItemsFromKeychain failed to remove this cert with status \(removeCertQueryStatus)")
|
||||
}
|
||||
}
|
||||
}
|
||||
print("deleteAllItemsFromKeychain - UUID")
|
||||
let removeUUIDQuery: NSDictionary = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrLabel: "kdeconnect-uuid",
|
||||
] as NSDictionary
|
||||
let removeUUIDQueryStatus: OSStatus = SecItemDelete(removeUUIDQuery)
|
||||
if removeUUIDQueryStatus != errSecSuccess {
|
||||
print("deleteAllItemsFromKeychain failed to remove UUID with status \(removeUUIDQueryStatus)")
|
||||
}
|
||||
#endif
|
||||
return (errSecSuccess != 0)
|
||||
}
|
||||
|
||||
// FIXME: the temp remote cert functions are here because I dind't find a way to do this from Objective-C inside LanLink.
|
||||
|
||||
@@ -11,6 +11,7 @@ struct AdvancedSettingsView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
backgroundService.stopDiscovery()
|
||||
CertificateService.shared.deleteAllItemsFromKeychain()
|
||||
@@ -28,10 +29,12 @@ struct AdvancedSettingsView: View {
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}.buttonStyle(.plain)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
backgroundService.stopDiscovery()
|
||||
CertificateService.shared.deleteHostCertificateFromKeychain()
|
||||
CertificateService.deleteHostCertificateFromKeychain()
|
||||
exit(0)
|
||||
} label: {
|
||||
HStack {
|
||||
@@ -45,6 +48,31 @@ struct AdvancedSettingsView: View {
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}.buttonStyle(.plain)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
backgroundService.stopDiscovery()
|
||||
// ref: https://stackoverflow.com/questions/43402032/how-to-remove-all-userdefaults-data-swift
|
||||
let domain = Bundle.main.bundleIdentifier!
|
||||
UserDefaults.standard.removePersistentDomain(forName: domain)
|
||||
UserDefaults.standard.synchronize()
|
||||
print("deleted settings, remaining: \(Array(UserDefaults.standard.dictionaryRepresentation().keys).count)")
|
||||
CertificateService.shared.deleteAllItemsFromKeychain()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "delete.right")
|
||||
VStack(alignment: .leading) {
|
||||
Text("Forget all")
|
||||
.font(.headline)
|
||||
Text("Delete all saved settings and devices. Requires the app to be fully restarted.")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}.buttonStyle(.plain)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
}.padding(.all)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ struct SettingsAdvancedView: View {
|
||||
|
||||
Button {
|
||||
notificationHapticsGenerator.notificationOccurred(.warning)
|
||||
CertificateService.shared.deleteHostCertificateFromKeychain()
|
||||
CertificateService.deleteHostCertificateFromKeychain()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "delete.right")
|
||||
@@ -82,6 +82,27 @@ struct SettingsAdvancedView: View {
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
|
||||
Button {
|
||||
notificationHapticsGenerator.notificationOccurred(.warning)
|
||||
// ref: https://stackoverflow.com/questions/43402032/how-to-remove-all-userdefaults-data-swift
|
||||
let domain = Bundle.main.bundleIdentifier!
|
||||
UserDefaults.standard.removePersistentDomain(forName: domain)
|
||||
UserDefaults.standard.synchronize()
|
||||
print("deleted settings, remaining: \(Array(UserDefaults.standard.dictionaryRepresentation().keys).count)")
|
||||
CertificateService.shared.deleteAllItemsFromKeychain()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "delete.right")
|
||||
VStack(alignment: .leading) {
|
||||
Text("Forget all")
|
||||
.font(.headline)
|
||||
Text("Delete all saved settings and devices. Requires the app to be fully restarted.")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
if kdeConnectSettings.isDebugging {
|
||||
|
||||
Reference in New Issue
Block a user