0

[iOS] Make the gaia available to the CPE when we have no credentials

Previously, the gaia was taken from existing credentials, which works
as long as there is already at least one credential in the database.
If there are no credentials in the database, we need to access the
current user's gaia in order to create new passwords or passkeys, so
we instead use the ios keychain for that purpose.

Low-Coverage-Reason: TRIVIAL_CHANGE to existing code and mock class
Bug: 355042671
Change-Id: Iad8f7625695563caf82152db07181a7cbaa44f91
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5958607
Commit-Queue: Alexis Hétu <sugoi@chromium.org>
Reviewed-by: Tommy Martino <tmartino@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1373574}
This commit is contained in:
Alexis Hetu
2024-10-24 21:01:24 +00:00
committed by Chromium LUCI CQ
parent b74efb8957
commit 7e9eefee11
14 changed files with 155 additions and 25 deletions

@ -68,6 +68,7 @@ if (ios_enable_credential_provider_extension) {
"//ios/chrome/common/credential_provider:ui",
"//ios/chrome/common/ui/favicon:favicon",
"//ios/chrome/common/ui/favicon:favicon_constants",
"//ios/components/credential_provider_extension:password_util",
"//ios/web/public",
"//url",
]

@ -6,4 +6,5 @@ include_rules = [
"+ios/chrome/browser/favicon/model",
"+ios/chrome/browser/passwords/model",
"+ios/chrome/browser/webauthn/model",
"+ios/components/credential_provider_extension",
]

@ -93,6 +93,11 @@ class CredentialProviderService
// Syncs the credential store to disk.
void SyncStore();
// Saves the Gaia of the current user to the ios keychain for the Credential
// Provider Extension to use when creating new credentials. Returns whether
// writing the data to the ios keychain succeeded.
bool SaveGaia();
// Add credentials from `forms`. Currently simply calls either the legacy or
// refactored version of this function.
void AddCredentials(MemoryCredentialStore* store,

@ -37,6 +37,7 @@
#import "ios/chrome/common/credential_provider/archivable_credential+passkey.h"
#import "ios/chrome/common/credential_provider/constants.h"
#import "ios/chrome/common/credential_provider/credential_store.h"
#import "ios/components/credential_provider_extension/password_util.h"
namespace {
@ -313,6 +314,13 @@ void CredentialProviderService::SyncAllCredentials(
SyncStore();
}
bool CredentialProviderService::SaveGaia() {
CoreAccountInfo account =
identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
return credential_provider_extension::StoreGaiaInKeychain(
base::SysUTF8ToNSString(account.gaia));
}
void CredentialProviderService::SyncStore() {
base::UmaHistogramBoolean(kSyncStoreHistogramName, true);
@ -329,6 +337,9 @@ void CredentialProviderService::SyncStore() {
if (error) {
return;
}
if (!SaveGaia()) {
return;
}
if (weak_credential_store) {
SyncASIdentityStore(weak_credential_store);
}

@ -49,6 +49,7 @@ source_set("credential_provider") {
"//ios/chrome/credential_provider_extension/ui",
"//ios/chrome/credential_provider_extension/ui:credential_response_handler",
"//ios/chrome/credential_provider_extension/ui:feature_flags",
"//ios/components/credential_provider_extension:password_util",
]
frameworks = [

@ -34,6 +34,7 @@
#import "ios/chrome/credential_provider_extension/ui/passkey_welcome_screen_view_controller.h"
#import "ios/chrome/credential_provider_extension/ui/saving_enterprise_disabled_view_controller.h"
#import "ios/chrome/credential_provider_extension/ui/stale_credentials_view_controller.h"
#import "ios/components/credential_provider_extension/password_util.h"
namespace {
UIColor* BackgroundColor() {
@ -431,6 +432,23 @@ UIColor* BackgroundColor() {
[self.extensionContext completeExtensionConfigurationRequest];
}
- (NSString*)gaia {
NSString* gaia = credential_provider_extension::LoadGaiaFromKeychain();
if (gaia.length > 0) {
return gaia;
}
// As a fallback, attempt to get a valid gaia from existing credentials.
NSArray<id<Credential>>* credentials = self.credentialStore.credentials;
NSUInteger credentialIndex =
[credentials indexOfObjectPassingTest:^BOOL(id<Credential> credential,
NSUInteger idx, BOOL* stop) {
return credential.gaia.length > 0;
}];
return credentialIndex != NSNotFound ? credentials[credentialIndex].gaia
: nil;
}
#pragma mark - PasskeyKeychainProviderBridgeDelegate
- (void)showEnrollmentWelcomeScreen:(ProceduralBlock)enrollBlock {
@ -755,21 +773,6 @@ UIColor* BackgroundColor() {
}
}
// Returns the gaia for the account used for passkey creation.
- (NSString*)gaia {
// TODO(crbug.com/355041765): Get gaia from ios keychain instead of the
// credential store, since that would fail if there are no synced credentials
// in the store.
NSArray<id<Credential>>* credentials = self.credentialStore.credentials;
NSUInteger credentialIndex =
[credentials indexOfObjectPassingTest:^BOOL(id<Credential> credential,
NSUInteger idx, BOOL* stop) {
return credential.gaia.length > 0;
}];
return credentialIndex != NSNotFound ? credentials[credentialIndex].gaia
: nil;
}
// Triggers the process to fetch the security domain secret and calls the
// completion block with the security domain secret as input.
- (void)fetchSecurityDomainSecretForGaia:(NSString*)gaia

@ -39,6 +39,9 @@ typedef void (^FetchSecurityDomainSecretCompletionBlock)(
- (void)completeExtensionConfigurationRequest;
// Returns the gaia for the account used for credential creation.
- (NSString*)gaia;
@end
#endif // IOS_CHROME_CREDENTIAL_PROVIDER_EXTENSION_UI_CREDENTIAL_RESPONSE_HANDLER_H_

@ -57,4 +57,8 @@ NSData* SecurityDomainSecret() {
// No-op.
}
- (NSString*)gaia {
return nil;
}
@end

@ -110,6 +110,10 @@ using base::SysUTF16ToNSString;
}];
}
- (NSString*)gaia {
return [self.credentialResponseHandler gaia];
}
#pragma mark - Private
// Checks whether a credential already exists with the given username.

@ -36,6 +36,9 @@
gaia:(NSString*)gaia
shouldReplace:(BOOL)shouldReplace;
// Returns the gaia for the account used for credential creation.
- (NSString*)gaia;
@end
// View Controller where a user can create a new credential and use a suggested

@ -381,16 +381,12 @@ typedef NS_ENUM(NSInteger, SectionIdentifier) {
NSString* password = self.passwordText;
NSString* note = self.noteText;
// TODO(crbug.com/330355124): Get the gaia ID if there's only 1 account OR
// show some UI so that the user can pick which account to create the password
// in.
NSString* gaia = nil;
[self.credentialHandler saveCredentialWithUsername:username
password:password
note:note
gaia:gaia
shouldReplace:shouldReplace];
[self.credentialHandler
saveCredentialWithUsername:username
password:password
note:note
gaia:[self.credentialHandler gaia]
shouldReplace:shouldReplace];
}
- (NSString*)noteFooterText {

@ -17,6 +17,14 @@ NSString* PasswordWithKeychainIdentifier(NSString* identifier);
// for later query. Returns `YES` if saving was successful and `NO` otherwise.
BOOL StorePasswordInKeychain(NSString* password, NSString* identifier);
// Queries Keychain Services for the stored gaia.
// Returns nil if no gaia is found.
NSString* LoadGaiaFromKeychain();
// Stores `gaia` in Keychain Services.
// Returns `YES` if saving was successful and `NO` otherwise.
BOOL StoreGaiaInKeychain(NSString* gaia);
} // namespace credential_provider_extension
#endif // IOS_COMPONENTS_CREDENTIAL_PROVIDER_EXTENSION_PASSWORD_UTIL_H_

@ -8,6 +8,41 @@
#import "base/logging.h"
namespace {
constexpr NSString* kGaiaIdentifier = @"credential_provider_extension.gaia";
NSDictionary* GaiaLoadQuery() {
return @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : kGaiaIdentifier,
(__bridge id)kSecReturnData : @YES,
};
}
NSDictionary* GaiaStoreQuery(NSData* gaia) {
return @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)
kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
(__bridge id)kSecValueData : gaia,
(__bridge id)kSecAttrAccount : kGaiaIdentifier,
};
}
NSDictionary* GaiaUpdateQuery() {
return @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : kGaiaIdentifier,
};
}
NSDictionary* GaiaUpdateAttribs(NSData* gaia) {
return @{(__bridge id)kSecValueData : gaia};
}
} // namespace
namespace credential_provider_extension {
NSString* PasswordWithKeychainIdentifier(NSString* identifier) {
@ -55,4 +90,45 @@ BOOL StorePasswordInKeychain(NSString* password, NSString* identifier) {
return status == errSecSuccess;
}
NSString* LoadGaiaFromKeychain() {
NSDictionary* gaiaLoadQuery = GaiaLoadQuery();
// Get the keychain item containing the password.
CFDataRef sec_data_ref = nullptr;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)gaiaLoadQuery,
(CFTypeRef*)&sec_data_ref);
if (status != errSecSuccess) {
DLOG(ERROR) << "Error retrieving gaia, OSStatus: " << status;
return nil;
}
// This is safe because SecItemCopyMatching either assign an owned reference
// to sec_data_ref, or leave it unchanged, and bridging maps nullptr to nil.
NSData* data = (__bridge_transfer NSData*)sec_data_ref;
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
BOOL StoreGaiaInKeychain(NSString* gaia) {
NSData* gaiaData = [gaia dataUsingEncoding:NSUTF8StringEncoding];
// Check that there is not already a gaia.
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)GaiaLoadQuery(),
/*result=*/nullptr);
if (status == errSecItemNotFound) {
// A new entry must be created.
status = SecItemAdd((__bridge CFDictionaryRef)GaiaStoreQuery(gaiaData),
/*result=*/nullptr);
} else if (status == noErr) {
// The entry must be updated.
status =
SecItemUpdate((__bridge CFDictionaryRef)GaiaUpdateQuery(),
(__bridge CFDictionaryRef)GaiaUpdateAttribs(gaiaData));
}
return status == errSecSuccess;
}
} // namespace credential_provider_extension

@ -130,4 +130,18 @@ TEST_F(PasswordUtilKeychainTest, StoreEmptyIdentifier) {
EXPECT_FALSE(StorePasswordInKeychain(kCredentialPassword1, @""));
}
// Tests storing and loading a gaia.
TEST_F(PasswordUtilKeychainTest, StoreGaia) {
EXPECT_TRUE(StoreGaiaInKeychain(kCredentialKey1));
EXPECT_NSEQ(LoadGaiaFromKeychain(), kCredentialKey1);
}
// Tests updating an existing gaia.
TEST_F(PasswordUtilKeychainTest, UpdateGaia) {
EXPECT_TRUE(StoreGaiaInKeychain(kCredentialKey1));
EXPECT_NSEQ(LoadGaiaFromKeychain(), kCredentialKey1);
EXPECT_TRUE(StoreGaiaInKeychain(kCredentialKey2));
EXPECT_NSEQ(LoadGaiaFromKeychain(), kCredentialKey2);
}
} // credential_provider_extension