Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mobile/ios/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,7 @@
<string>Photos access is required to give you the ability to send images.</string>
<key>NSAppleMusicUsageDescription</key>
<string>Status uses Media Library to save and send Images. The Media Library module internally requires permissions to Apple Music</string>
<key>NSFaceIDUsageDescription</key>
<string>Log in securely to your account.</string>
</dict>
</plist>
6 changes: 6 additions & 0 deletions mobile/wrapperApp/Status-tablet.pro
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,10 @@ ios {
QMAKE_ASSET_CATALOGS += $$PWD/../ios/Images.xcassets

LIBS += -L$$PWD/../lib/$$LIB_PREFIX -lnim_status_client -lDOtherSideStatic -lstatusq -lstatus -lssl_3 -lcrypto_3 -lqzxing -lresolv -lqrcodegen

# --- iOS frameworks required by keychain_apple.mm ---
LIBS += -framework LocalAuthentication \
-framework Security \
-framework UIKit \
-framework Foundation
}
4 changes: 2 additions & 2 deletions ui/StatusQ/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,13 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_link_libraries(${PROJECT_NAME} PRIVATE ${AppKit} ${Foundation} ${Security} ${LocalAuthentication})
target_sources(StatusQ PRIVATE
src/statuswindow_osx.mm
src/keychain_osx.mm
src/keychain_apple.mm
)
elseif (${CMAKE_SYSTEM_NAME} MATCHES "iOS")
target_sources(StatusQ PRIVATE
src/ios_utils.mm
src/statuswindow_other.cpp
src/keychain_other.cpp
src/keychain_apple.mm
)
else ()
target_sources(StatusQ PRIVATE
Expand Down
2 changes: 1 addition & 1 deletion ui/StatusQ/include/StatusQ/keychain.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class Keychain : public QObject {
QFuture<void> m_future;
LAContext *m_activeAuthContext;

#ifdef Q_OS_MACOS
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
Status getCredential(const QString &reason, const QString &account, QString *out);
void reevaluateAvailability();
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@
#include <LocalAuthentication/LocalAuthentication.h>
#include <Security/Security.h>

#if TARGET_OS_OSX
const static auto authPolicy =
#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000
LAPolicyDeviceOwnerAuthenticationWithBiometricsOrCompanion;
#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101202
LAPolicyDeviceOwnerAuthenticationWithBiometrics;
#else
#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000
LAPolicyDeviceOwnerAuthenticationWithBiometricsOrCompanion;
#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101202
LAPolicyDeviceOwnerAuthenticationWithBiometrics;
#else
LAPolicyDeviceOwnerAuthentication;
#endif
#elif TARGET_OS_IPHONE
const static LAPolicy authPolicy =
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
LAPolicyDeviceOwnerAuthenticationWithBiometrics;
#else
LAPolicyDeviceOwnerAuthentication;
#endif
#else
const static LAPolicy authPolicy = LAPolicyDeviceOwnerAuthentication;
#endif

static Keychain::Status convertStatus(OSStatus status)
Expand All @@ -26,8 +37,16 @@
return Keychain::StatusSuccess;
case errSecItemNotFound:
return Keychain::StatusNotFound;
#if defined(errSecCSCancelled)
// Present on macOS SDKs
case errSecCSCancelled:
return Keychain::StatusCancelled;
#endif
#if defined(errSecUserCanceled)
// Present on iOS (and also macOS); treat as the same "user cancelled" outcome
case errSecUserCanceled:
return Keychain::StatusCancelled;
#endif
default:
return Keychain::StatusGenericError;
}
Expand Down Expand Up @@ -138,7 +157,16 @@
Keychain::Status Keychain::saveCredential(const QString &account, const QString &password)
{
CFErrorRef error = NULL;
auto flags = kSecAccessControlBiometryCurrentSet | kSecAccessControlOr | kSecAccessControlWatch;

// On iOS there is no Apple Watch companion unlock; keep flags minimal.
// We still create an access control object even if it's not added to the query (left commented below),
// to keep parity with macOS and make it easy to enable later.
#if TARGET_OS_OSX
auto flags = kSecAccessControlBiometryCurrentSet | kSecAccessControlOr | kSecAccessControlWatch;
#else
auto flags = kSecAccessControlBiometryCurrentSet;
#endif

auto accessControl = SecAccessControlCreateWithFlags(NULL,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags,
Expand All @@ -157,7 +185,8 @@
(__bridge id) kSecAttrService: m_service.toNSString(),
(__bridge id) kSecAttrAccount: account.toNSString(),
(__bridge id) kSecValueData: [password.toNSString() dataUsingEncoding:NSUTF8StringEncoding],
// (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
// (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl, // enable if you want Keychain to enforce biometrics

};

SecItemDelete((__bridge CFDictionaryRef) query); // Ensure old item is removed
Expand Down Expand Up @@ -208,16 +237,27 @@
(__bridge id) kSecUseAuthenticationContext: m_activeAuthContext,
};

// Use the LAContext with Keychain when available. iOS 11+/macOS 10.13+ support kSecUseAuthenticationContext.
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
(defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300)
NSMutableDictionary *mutableQuery = [query mutableCopy];
mutableQuery[(__bridge id)kSecUseAuthenticationContext] = m_activeAuthContext;
query = mutableQuery;
#endif

CFDataRef data = NULL;

const auto status = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef *) &data);

// Convert and release CF data on success.
if (out != nullptr) {
auto dataString = [[NSString alloc] initWithData:(__bridge NSData *) data
encoding:NSUTF8StringEncoding];
*out = QString::fromNSString(dataString);
}

if (data) CFRelease(data);

return convertStatus(status);
}

Expand Down