123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- /*
- * Copyright 2019 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- #import "FIRInstanceIDKeyPairStore.h"
-
- #import "FIRInstanceIDBackupExcludedPlist.h"
- #import "FIRInstanceIDConstants.h"
- #import "FIRInstanceIDKeyPair.h"
- #import "FIRInstanceIDKeyPairUtilities.h"
- #import "FIRInstanceIDKeychain.h"
- #import "FIRInstanceIDLogger.h"
- #import "FIRInstanceIDUtilities.h"
- #import "NSError+FIRInstanceID.h"
-
- // NOTE: These values should be in sync with what InstanceID saves in as.
- static NSString *const kFIRInstanceIDKeyPairStoreFileName = @"com.google.iid-keypair";
-
- static NSString *const kFIRInstanceIDStoreKeyGenerationTime = @"cre";
-
- static NSString *const kFIRInstanceIDStoreKeyPrefix = @"com.google.iid-";
- static NSString *const kFIRInstanceIDStoreKeyPublic = @"|P|";
- static NSString *const kFIRInstanceIDStoreKeyPrivate = @"|K|";
- static NSString *const kFIRInstanceIDStoreKeySubtype = @"|S|";
-
- static NSString *const kFIRInstanceIDKeyPairPublicTagPrefix = @"com.google.iid.keypair.public-";
- static NSString *const kFIRInstanceIDKeyPairPrivateTagPrefix = @"com.google.iid.keypair.private-";
-
- static const int kMaxMissingEntitlementErrorCount = 3;
-
- NSString *const kFIRInstanceIDKeyPairSubType = @"";
-
- // Query the key with NSData format
- NSData *FIRInstanceIDKeyDataWithTag(NSString *tag) {
- if (![tag length]) {
- return NULL;
- }
- NSDictionary *queryKey = FIRInstanceIDKeyPairQuery(tag, YES, YES);
- CFTypeRef result = [[FIRInstanceIDKeychain sharedInstance] itemWithQuery:queryKey];
- if (!result) {
- return NULL;
- }
- return (__bridge NSData *)result;
- }
-
- // Query the key given a tag
- SecKeyRef FIRInstanceIDCachedKeyRefWithTag(NSString *tag) {
- if (!tag.length) {
- return NULL;
- }
- NSDictionary *queryKey = FIRInstanceIDKeyPairQuery(tag, YES, NO);
- CFTypeRef result = [[FIRInstanceIDKeychain sharedInstance] itemWithQuery:queryKey];
- return (SecKeyRef)result;
- }
-
- // Check if keypair has been migrated from the legacy to the new version
- BOOL FIRInstanceIDHasMigratedKeyPair(NSString *legacyPublicKeyTag, NSString *newPublicKeyTag) {
- NSData *oldPublicKeyData = FIRInstanceIDKeyDataWithTag(legacyPublicKeyTag);
- NSData *newPublicKeyData = FIRInstanceIDKeyDataWithTag(newPublicKeyTag);
- return [oldPublicKeyData isEqualToData:newPublicKeyData];
- }
-
- // The legacy value is hardcoded to be the same key. This is a potential problem in shared keychain
- // environments.
- NSString *FIRInstanceIDLegacyPublicTagWithSubtype(NSString *subtype) {
- NSString *prefix = kFIRInstanceIDStoreKeyPrefix;
- return [NSString stringWithFormat:@"%@%@%@", prefix, subtype, kFIRInstanceIDStoreKeyPublic];
- }
-
- // The legacy value is hardcoded to be the same key. This is a potential problem in shared keychain
- // environments.
- NSString *FIRInstanceIDLegacyPrivateTagWithSubtype(NSString *subtype) {
- NSString *prefix = kFIRInstanceIDStoreKeyPrefix;
- return [NSString stringWithFormat:@"%@%@%@", prefix, subtype, kFIRInstanceIDStoreKeyPrivate];
- }
-
- NSString *FIRInstanceIDPublicTagWithSubtype(NSString *subtype) {
- static NSString *publicTag;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSString *mainAppBundleID = FIRInstanceIDAppIdentifier();
- publicTag =
- [NSString stringWithFormat:@"%@%@", kFIRInstanceIDKeyPairPublicTagPrefix, mainAppBundleID];
- });
- return publicTag;
- }
-
- NSString *FIRInstanceIDPrivateTagWithSubtype(NSString *subtype) {
- static NSString *privateTag;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSString *mainAppBundleID = FIRInstanceIDAppIdentifier();
- privateTag =
- [NSString stringWithFormat:@"%@%@", kFIRInstanceIDKeyPairPrivateTagPrefix, mainAppBundleID];
- });
- return privateTag;
- }
-
- NSString *FIRInstanceIDCreationTimeKeyWithSubtype(NSString *subtype) {
- return [NSString stringWithFormat:@"%@%@%@", subtype, kFIRInstanceIDStoreKeySubtype,
- kFIRInstanceIDStoreKeyGenerationTime];
- }
-
- @interface FIRInstanceIDKeyPairStore ()
-
- @property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *plist;
- @property(atomic, readwrite, strong) FIRInstanceIDKeyPair *keyPair;
- @property(nonatomic, readwrite, assign) NSInteger keychainEntitlementsErrorCount;
-
- @end
-
- @implementation FIRInstanceIDKeyPairStore
-
- - (instancetype)init {
- self = [super init];
- if (self) {
- NSString *fileName = [[self class] keyStoreFileName];
- _plist =
- [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:fileName
- subDirectory:kFIRInstanceIDSubDirectoryName];
- }
- return self;
- }
-
- - (BOOL)invalidateKeyPairsIfNeeded {
- // Currently keypairs are always invalidated if self.plist is missing. This normally indicates
- // a fresh install (or an uninstall/reinstall). In those situations the key pairs should be
- // deleted.
- // NOTE: Although this class refers to multiple key pairs, with different subtypes, in practice
- // only a single subtype is currently supported. (b/64906549)
- if (![self.plist doesFileExist]) {
- // A fresh install, clear all the key pairs in the key chain. Do not perform migration as all
- // key pairs are gone.
- [self deleteSavedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType handler:nil];
- return YES;
- }
- // Not a fresh install, perform migration at early state.
- [self migrateKeyPairCacheIfNeededWithHandler:nil];
- return NO;
- }
-
- - (BOOL)hasCachedKeyPairs {
- NSError *error;
- if ([self cachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:&error] == nil) {
- if (error) {
- FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore000,
- @"Failed to get the cached keyPair %@", error);
- }
- error = nil;
- [self removeKeyPairCreationTimePlistWithError:&error];
- if (error) {
- FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore001,
- @"Failed to remove keyPair creationTime plist %@", error);
- }
- return NO;
- }
- return YES;
- }
-
- - (NSString *)appIdentityWithError:(NSError *__autoreleasing *)error {
- // Load the keyPair from Keychain (or generate a key pair, if this is the first run of the app).
- FIRInstanceIDKeyPair *keyPair = [self loadKeyPairWithError:error];
- if (!keyPair) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStoreCouldNotLoadKeyPair,
- @"Keypair could not be loaded from Keychain. Error: %@", (*error));
- return nil;
- }
-
- if (error) {
- *error = nil;
- }
- NSString *appIdentity = FIRInstanceIDAppIdentity(keyPair);
- if (!appIdentity.length) {
- if (error) {
- *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
- }
- }
- return appIdentity;
- }
-
- - (FIRInstanceIDKeyPair *)loadKeyPairWithError:(NSError **)error {
- // In case we call this from different threads we don't want to generate or fetch the
- // keyPair multiple times. Once we have a keyPair in the cache it would mostly be used
- // from there.
- @synchronized(self) {
- if ([self.keyPair isValid]) {
- return self.keyPair;
- }
-
- if (self.keychainEntitlementsErrorCount >= kMaxMissingEntitlementErrorCount) {
- FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore002,
- @"Keychain not accessible, Entitlements missing error (-34018). "
- @"Will not check token in cache.");
- return nil;
- }
-
- if (!self.keyPair) {
- self.keyPair = [self validCachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:error];
- }
-
- if ((*error).code == kFIRInstanceIDSecMissingEntitlementErrorCode) {
- self.keychainEntitlementsErrorCount++;
- }
-
- if (!self.keyPair) {
- self.keyPair = [self generateAndSaveKeyWithSubtype:kFIRInstanceIDKeyPairSubType
- creationTime:FIRInstanceIDCurrentTimestampInSeconds()
- error:error];
- }
- }
- return self.keyPair;
- }
-
- // TODO(chliangGoogle: Remove subtype support, as it's not being used.
- - (FIRInstanceIDKeyPair *)generateAndSaveKeyWithSubtype:(NSString *)subtype
- creationTime:(int64_t)creationTime
- error:(NSError **)error {
- NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(subtype);
- NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(subtype);
- FIRInstanceIDKeyPair *keyPair =
- [[FIRInstanceIDKeychain sharedInstance] generateKeyPairWithPrivateTag:privateKeyTag
- publicTag:publicKeyTag];
-
- if (![keyPair isValid]) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore003,
- @"Unable to generate keypair.");
- return nil;
- }
-
- NSString *creationTimeKey = FIRInstanceIDCreationTimeKeyWithSubtype(subtype);
- NSDictionary *keyPairData = @{creationTimeKey : @(creationTime)};
-
- if (error) {
- *error = nil;
- }
- NSMutableDictionary *allKeyPairs = [[self.plist contentAsDictionary] mutableCopy];
- if (allKeyPairs.count) {
- [allKeyPairs addEntriesFromDictionary:keyPairData];
- } else {
- allKeyPairs = [keyPairData mutableCopy];
- }
- if (![self.plist writeDictionary:allKeyPairs error:error]) {
- [FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:privateKeyTag
- publicTag:publicKeyTag
- handler:nil];
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore004,
- @"Failed to save keypair data to plist %@", error ? *error : @"");
- return nil;
- }
-
- return keyPair;
- }
-
- - (FIRInstanceIDKeyPair *)validCachedKeyPairWithSubtype:(NSString *)subtype
- error:(NSError **)error {
- // On a new install (or if the ID was deleted), the plist will be missing, which should trigger
- // a reset of the key pairs in Keychain (if they exist).
- NSDictionary *allKeyPairs = [self.plist contentAsDictionary];
- NSString *creationTimeKey = FIRInstanceIDCreationTimeKeyWithSubtype(subtype);
-
- if (allKeyPairs[creationTimeKey] > 0) {
- return [self cachedKeyPairWithSubtype:subtype error:error];
- } else {
- // There is no need to reset keypair again here as FIRInstanceID init call is always
- // going to be ahead of this call, which already trigger keypair reset if it's new install
- FIRInstanceIDErrorCode code = kFIRInstanceIDErrorCodeInvalidKeyPairCreationTime;
- if (error) {
- *error = [NSError errorWithFIRInstanceIDErrorCode:code];
- }
- return nil;
- }
- }
-
- - (FIRInstanceIDKeyPair *)cachedKeyPairWithSubtype:(NSString *)subtype
- error:(NSError *__autoreleasing *)error {
- // base64 encoded keys
- NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(subtype);
- NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(subtype);
- return [FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:privateKeyTag
- publicKeyTag:publicKeyTag
- error:error];
- }
-
- + (FIRInstanceIDKeyPair *)keyPairForPrivateKeyTag:(NSString *)privateKeyTag
- publicKeyTag:(NSString *)publicKeyTag
- error:(NSError *__autoreleasing *)error {
- if (![privateKeyTag length] || ![publicKeyTag length]) {
- if (error) {
- *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPairTags];
- }
- return nil;
- }
-
- SecKeyRef privateKeyRef = FIRInstanceIDCachedKeyRefWithTag(privateKeyTag);
- SecKeyRef publicKeyRef = FIRInstanceIDCachedKeyRefWithTag(publicKeyTag);
-
- if (!privateKeyRef || !publicKeyRef) {
- if (error) {
- *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeMissingKeyPair];
- }
- FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPair000,
- @"No keypair info is found with tag %@", privateKeyTag);
- return nil;
- }
-
- NSData *publicKeyData = FIRInstanceIDKeyDataWithTag(publicKeyTag);
- NSData *privateKeyData = FIRInstanceIDKeyDataWithTag(privateKeyTag);
-
- FIRInstanceIDKeyPair *keyPair = [[FIRInstanceIDKeyPair alloc] initWithPrivateKey:privateKeyRef
- publicKey:publicKeyRef
- publicKeyData:publicKeyData
- privateKeyData:privateKeyData];
- return keyPair;
- }
-
- // Migrates from keypair saved under legacy keys (hardcoded value) to dynamic keys (stable, but
- // unique for the app's bundle id
- - (void)migrateKeyPairCacheIfNeededWithHandler:(void (^)(NSError *error))handler {
- // Attempt to load keypair using legacy keys
- NSString *legacyPublicKeyTag =
- FIRInstanceIDLegacyPublicTagWithSubtype(kFIRInstanceIDKeyPairSubType);
- NSString *legacyPrivateKeyTag =
- FIRInstanceIDLegacyPrivateTagWithSubtype(kFIRInstanceIDKeyPairSubType);
- NSError *error;
- FIRInstanceIDKeyPair *keyPair =
- [FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:legacyPrivateKeyTag
- publicKeyTag:legacyPublicKeyTag
- error:&error];
- if (![keyPair isValid]) {
- FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairNoLegacyKeyPair,
- @"There's no legacy keypair so no need to do migration.");
- if (handler) {
- handler(nil);
- }
- return;
- }
-
- // Check whether migration already done.
- NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(kFIRInstanceIDKeyPairSubType);
- if (FIRInstanceIDHasMigratedKeyPair(legacyPublicKeyTag, publicKeyTag)) {
- if (handler) {
- handler(nil);
- }
- return;
- }
-
- // Also cache locally since we are sure to use the migrated key pair.
- self.keyPair = keyPair;
-
- // Either new key pair doesn't exist or it's different than legacy key pair, start the migration.
- __block NSError *updateKeyRefError;
-
- NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(kFIRInstanceIDKeyPairSubType);
- [self updateKeyRef:keyPair.publicKey
- withTag:publicKeyTag
- handler:^(NSError *error) {
- if (error) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairMigrationError,
- @"Unable to migrate key pair from legacy ones.");
- updateKeyRefError = error;
- }
- }];
-
- [self updateKeyRef:keyPair.privateKey
- withTag:privateKeyTag
- handler:^(NSError *error) {
- if (error) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairMigrationError,
- @"Unable to migrate key pair from legacy ones.");
- updateKeyRefError = error;
- }
-
- if (handler) {
- handler(updateKeyRefError);
- }
- }];
- }
-
- // Used for migrating from legacy tags to updated tags. The legacy keychain is not deleted for
- // backward compatibility.
- // TODO(chliangGoogle) Delete the legacy keychain when GCM is fully deprecated.
- - (void)updateKeyRef:(SecKeyRef)keyRef
- withTag:(NSString *)tag
- handler:(void (^)(NSError *error))handler {
- NSData *updatedTagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
-
- __block NSError *keychainError;
-
- // Always delete the old keychain before adding a new one to avoid conflicts.
- NSDictionary *deleteQuery = @{
- (__bridge id)kSecAttrApplicationTag : updatedTagData,
- (__bridge id)kSecClass : (__bridge id)kSecClassKey,
- (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeRSA,
- (__bridge id)kSecReturnRef : @(YES),
- };
- [[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:deleteQuery
- handler:^(NSError *error) {
- if (error) {
- keychainError = error;
- }
- }];
-
- NSDictionary *addQuery = @{
- (__bridge id)kSecAttrApplicationTag : updatedTagData,
- (__bridge id)kSecClass : (__bridge id)kSecClassKey,
- (__bridge id)kSecValueRef : (__bridge id)keyRef,
- (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly,
- };
- [[FIRInstanceIDKeychain sharedInstance] addItemWithQuery:addQuery
- handler:^(NSError *addError) {
- if (addError) {
- keychainError = addError;
- }
-
- if (handler) {
- handler(keychainError);
- }
- }];
- }
-
- - (void)deleteSavedKeyPairWithSubtype:(NSString *)subtype
- handler:(void (^)(NSError *error))handler {
- NSDictionary *allKeyPairs = [self.plist contentAsDictionary];
-
- NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(subtype);
- NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(subtype);
- NSString *creationTimeKey = FIRInstanceIDCreationTimeKeyWithSubtype(subtype);
-
- // remove the creation time
- if (allKeyPairs[creationTimeKey] > 0) {
- NSMutableDictionary *newKeyPairs = [NSMutableDictionary dictionaryWithDictionary:allKeyPairs];
- [newKeyPairs removeObjectForKey:creationTimeKey];
-
- NSError *plistError;
- if (![self.plist writeDictionary:newKeyPairs error:&plistError]) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore006,
- @"Unable to remove keypair creation time from plist %@", plistError);
- }
- }
-
- self.keyPair = nil;
-
- [FIRInstanceIDKeyPairStore
- deleteKeyPairWithPrivateTag:privateKeyTag
- publicTag:publicKeyTag
- handler:^(NSError *error) {
- // Delete legacy key pairs from GCM/FCM If they exist. All key pairs
- // should be deleted when app is newly installed.
- NSString *legacyPublicKeyTag =
- FIRInstanceIDLegacyPublicTagWithSubtype(subtype);
- NSString *legacyPrivateKeyTag =
- FIRInstanceIDLegacyPrivateTagWithSubtype(subtype);
- [FIRInstanceIDKeyPairStore
- deleteKeyPairWithPrivateTag:legacyPrivateKeyTag
- publicTag:legacyPublicKeyTag
- handler:nil];
- if (error) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore007,
- @"Unable to remove RSA keypair, error: %@",
- error);
- if (handler) {
- handler(error);
- }
- } else {
- if (handler) {
- handler(nil);
- }
- }
- }];
- }
-
- + (void)deleteKeyPairWithPrivateTag:(NSString *)privateTag
- publicTag:(NSString *)publicTag
- handler:(void (^)(NSError *))handler {
- NSDictionary *queryPublicKey = FIRInstanceIDKeyPairQuery(publicTag, NO, NO);
- NSDictionary *queryPrivateKey = FIRInstanceIDKeyPairQuery(privateTag, NO, NO);
-
- __block NSError *keychainError;
-
- // Always remove public key first because it is the key we generate IID.
- [[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:queryPublicKey
- handler:^(NSError *error) {
- if (error) {
- keychainError = error;
- }
- }];
-
- [[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:queryPrivateKey
- handler:^(NSError *error) {
- if (error) {
- keychainError = error;
- }
-
- if (handler) {
- handler(keychainError);
- }
- }];
- }
-
- - (BOOL)removeKeyPairCreationTimePlistWithError:(NSError *__autoreleasing *)error {
- if (![self.plist deleteFile:error]) {
- FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore008,
- @"Unable to delete keypair creation times plist");
- return NO;
- }
- return YES;
- }
-
- + (NSString *)keyStoreFileName {
- return kFIRInstanceIDKeyPairStoreFileName;
- }
-
- @end
|