Skip to content

Commit 1e23da7

Browse files
jonsimantova-maurice
authored andcommitted
Add MacOS implementation of secure user storage.
This stores the data in the default keychain, similar to how Auth iOS does so. PiperOrigin-RevId: 245332685
1 parent ccb5ee1 commit 1e23da7

7 files changed

+319
-2
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef FIREBASE_AUTH_CLIENT_CPP_SRC_DESKTOP_SECURE_USER_SECURE_DARWIN_INTERNAL_H_
16+
#define FIREBASE_AUTH_CLIENT_CPP_SRC_DESKTOP_SECURE_USER_SECURE_DARWIN_INTERNAL_H_
17+
18+
#include <string>
19+
20+
#include "auth/src/desktop/secure/user_secure_internal.h"
21+
22+
namespace firebase {
23+
namespace auth {
24+
namespace secure {
25+
26+
// Darwin-specific implementation for the secure manager of user data.
27+
class UserSecureDarwinInternal : public UserSecureInternal {
28+
public:
29+
explicit UserSecureDarwinInternal(const char* service);
30+
31+
~UserSecureDarwinInternal() override;
32+
33+
std::string LoadUserData(const std::string& app_name) override;
34+
35+
void SaveUserData(const std::string& app_name,
36+
const std::string& user_data) override;
37+
38+
void DeleteUserData(const std::string& app_name) override;
39+
40+
void DeleteAllData() override;
41+
42+
private:
43+
// Delete either a single key, or (if app_name is null) all keys.
44+
// func_name is used for error messages.
45+
void DeleteData(const char* app_name, const char* func_name);
46+
47+
std::string GetKeystoreLocation(const std::string& app);
48+
49+
std::string service_;
50+
};
51+
52+
} // namespace secure
53+
} // namespace auth
54+
} // namespace firebase
55+
56+
#endif // FIREBASE_AUTH_CLIENT_CPP_SRC_DESKTOP_SECURE_USER_SECURE_DARWIN_INTERNAL_H_
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "auth/src/desktop/secure/user_secure_darwin_internal.h"
16+
17+
#import <Foundation/Foundation.h>
18+
#import <Security/Security.h>
19+
20+
namespace firebase {
21+
namespace auth {
22+
namespace secure {
23+
24+
static const char kServicePrefix[] = "firebase.auth.cpp.cred/";
25+
static const int kMaxAllowedKeychainEntries = INT_MAX;
26+
27+
UserSecureDarwinInternal::UserSecureDarwinInternal(const char* service) {
28+
service_ = std::string(kServicePrefix) + service;
29+
}
30+
31+
UserSecureDarwinInternal::~UserSecureDarwinInternal() {}
32+
33+
NSMutableDictionary* GetQueryForApp(const char* service, const char* app) {
34+
// Create a query dictionary representing our service and the (optional) app name.
35+
// You can further narrow down this query to use it for saving or loading data.
36+
NSMutableDictionary* query = [[NSMutableDictionary alloc] init];
37+
query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
38+
query[(__bridge id)kSecAttrService] = @(service);
39+
if (app) {
40+
query[(__bridge id)kSecAttrAccount] = @(app);
41+
}
42+
return query;
43+
}
44+
45+
std::string UserSecureDarwinInternal::GetKeystoreLocation(const std::string& app) {
46+
return service_ + "/" + app;
47+
}
48+
49+
std::string UserSecureDarwinInternal::LoadUserData(const std::string& app_name) {
50+
NSMutableDictionary* query = GetQueryForApp(service_.c_str(), app_name.c_str());
51+
std::string keystore_location = GetKeystoreLocation(app_name);
52+
// We want to return the data and attributes.
53+
query[(__bridge id)kSecReturnData] = @YES;
54+
query[(__bridge id)kSecReturnAttributes] = @YES;
55+
// Request up to 2 matches, so we can find out if there is more than one, which is an error.
56+
query[(__bridge id)kSecMatchLimit] = @2;
57+
58+
CFArrayRef result = NULL;
59+
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&result);
60+
61+
if (status == errSecItemNotFound) {
62+
LogDebug("LoadUserData: Key %s not found", keystore_location.c_str());
63+
return "";
64+
} else if (status != noErr) {
65+
// Some other error occurred.
66+
NSString* error_string = (__bridge_transfer NSString*)SecCopyErrorMessageString(status, NULL);
67+
LogError("LoadUserData: Error %d reading %s: %s", status, keystore_location.c_str(),
68+
error_string.UTF8String);
69+
return "";
70+
}
71+
NSArray* items = (__bridge_transfer NSArray*)result;
72+
if (items.count > 1) {
73+
LogError("LoadUserData: Error reading %s: Returned %d entries instead of 1. Deleting "
74+
"extraneous entries.",
75+
keystore_location.c_str(), items.count);
76+
// Delete all the invalid data so it doesn't mess us up next time.
77+
DeleteUserData(app_name);
78+
return "";
79+
}
80+
NSDictionary* item = items[0];
81+
// Grab the data from the single item in the array.
82+
NSData* data = item[(__bridge id)kSecValueData];
83+
return std::string(reinterpret_cast<const char*>(data.bytes), data.length);
84+
}
85+
86+
void UserSecureDarwinInternal::SaveUserData(const std::string& app_name,
87+
const std::string& user_data) {
88+
DeleteUserData(app_name);
89+
NSMutableDictionary* query = GetQueryForApp(service_.c_str(), app_name.c_str());
90+
std::string keystore_location = GetKeystoreLocation(app_name);
91+
std::string comment =
92+
std::string("Firebase Auth persistent user data for ") + GetKeystoreLocation(app_name);
93+
// Set the binary data, what type of accesibility it should have, and a comment.
94+
NSDictionary* attributes = @{
95+
(__bridge id)
96+
kSecValueData : [NSData dataWithBytes:reinterpret_cast<const void*>(user_data.c_str())
97+
length:user_data.length()],
98+
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
99+
(__bridge id)kSecAttrComment : @(comment.c_str()),
100+
};
101+
102+
NSMutableDictionary* combined = [attributes mutableCopy];
103+
[combined addEntriesFromDictionary:query];
104+
105+
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)combined, nil);
106+
107+
if (status != noErr) {
108+
NSString* error_string = (__bridge_transfer NSString*)SecCopyErrorMessageString(status, NULL);
109+
LogError("SaveUserData: Error %d adding %s: %s", status, keystore_location.c_str(),
110+
error_string.UTF8String);
111+
return;
112+
}
113+
}
114+
115+
void UserSecureDarwinInternal::DeleteData(const char* app_name, const char* func_name) {
116+
NSMutableDictionary* query = GetQueryForApp(service_.c_str(), app_name);
117+
std::string keystore_location = app_name ? GetKeystoreLocation(app_name) : service_;
118+
119+
// In case keychain data is invalid and there is more than one
120+
// matching entry, set the match limit to delete them all.
121+
query[(__bridge id)kSecMatchLimit] = [NSNumber numberWithInt:INT_MAX];
122+
123+
// Delete a specific matching entry.
124+
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
125+
126+
if (status == errSecItemNotFound) {
127+
LogDebug("%s: Key %s not found", keystore_location.c_str());
128+
return;
129+
} else if (status != noErr) {
130+
NSString* error_string = (__bridge_transfer NSString*)SecCopyErrorMessageString(status, NULL);
131+
LogError("%s: Error %d deleting %s: %s", status, keystore_location.c_str(),
132+
error_string.UTF8String);
133+
return;
134+
}
135+
}
136+
137+
void UserSecureDarwinInternal::DeleteUserData(const std::string& app_name) {
138+
DeleteData(app_name.c_str(), "DeleteUserData");
139+
}
140+
141+
void UserSecureDarwinInternal::DeleteAllData() { DeleteData(nullptr, "DeleteAllData"); }
142+
143+
} // namespace secure
144+
} // namespace auth
145+
} // namespace firebase
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef FIREBASE_AUTH_CLIENT_CPP_SRC_DESKTOP_SECURE_USER_SECURE_DARWIN_INTERNAL_TESTLIB_H_
16+
#define FIREBASE_AUTH_CLIENT_CPP_SRC_DESKTOP_SECURE_USER_SECURE_DARWIN_INTERNAL_TESTLIB_H_
17+
18+
#include <string>
19+
20+
namespace firebase {
21+
namespace auth {
22+
namespace secure {
23+
24+
class UserSecureDarwinTestHelper {
25+
public:
26+
UserSecureDarwinTestHelper();
27+
~UserSecureDarwinTestHelper();
28+
29+
private:
30+
std::string test_keychain_file_;
31+
bool previous_interaction_mode_; // To restore previous mode to
32+
// SecKeychainSetUserInteractionAllowed
33+
};
34+
35+
} // namespace secure
36+
} // namespace auth
37+
} // namespace firebase
38+
39+
#endif // FIREBASE_AUTH_CLIENT_CPP_SRC_DESKTOP_SECURE_USER_SECURE_DARWIN_INTERNAL_TESTLIB_H_
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "auth/src/desktop/secure/user_secure_darwin_internal_testlib.h"
16+
17+
#include "app/src/log.h"
18+
19+
#import <Foundation/Foundation.h>
20+
#import <Security/Security.h>
21+
22+
namespace firebase {
23+
namespace auth {
24+
namespace secure {
25+
26+
static const char kTestKeychainPassword[] = "password";
27+
28+
UserSecureDarwinTestHelper::UserSecureDarwinTestHelper() {
29+
SecKeychainRef keychain;
30+
char temp_file[] = "test_keychain_XXXXXX";
31+
std::string filename = mktemp(temp_file);
32+
if (filename.empty()) {
33+
LogError("UserSecureDarwinTestHelper: Couldn't create temporary file");
34+
return;
35+
}
36+
OSStatus status = SecKeychainCreate(filename.c_str(), strlen(kTestKeychainPassword),
37+
kTestKeychainPassword, FALSE, NULL, &keychain);
38+
if (status != noErr) {
39+
NSString* error_string = (__bridge_transfer NSString*)SecCopyErrorMessageString(status, NULL);
40+
LogError("UserSecureDarwinTestHelper: Error %d creating %s: %s", status, filename.c_str(),
41+
error_string.UTF8String);
42+
return;
43+
}
44+
status = SecKeychainSetDefault(keychain);
45+
CFRelease(keychain);
46+
if (status != noErr) {
47+
NSString* error_string = (__bridge_transfer NSString*)SecCopyErrorMessageString(status, NULL);
48+
LogError("UserSecureDarwinTestHelper: Error %d setting default keychain to %s: %s", status,
49+
filename.c_str(), error_string.UTF8String);
50+
return;
51+
}
52+
LogInfo("UserSecureDarwinTestHelper: Using test keychain: %s", filename.c_str());
53+
test_keychain_file_ = filename;
54+
Boolean prev_interaction = YES;
55+
SecKeychainGetUserInteractionAllowed(&prev_interaction);
56+
previous_interaction_mode_ = (prev_interaction != NO);
57+
SecKeychainSetUserInteractionAllowed(NO);
58+
}
59+
60+
UserSecureDarwinTestHelper::~UserSecureDarwinTestHelper() {
61+
if (!test_keychain_file_.empty()) {
62+
unlink(test_keychain_file_.c_str());
63+
test_keychain_file_ = "";
64+
SecKeychainSetUserInteractionAllowed(previous_interaction_mode_ ? YES : NO);
65+
}
66+
}
67+
68+
} // namespace secure
69+
} // namespace auth
70+
} // namespace firebase

auth/src/desktop/secure/user_secure_fake_internal.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ void UserSecureFakeInternal::DeleteUserData(const std::string& app_name) {
7373
void UserSecureFakeInternal::DeleteAllData() {
7474
// These are data types defined in the "dirent" header
7575
DIR* theFolder = opendir(secure_path_.c_str());
76+
if (!theFolder) {
77+
return;
78+
}
7679
struct dirent* next_file;
7780

7881
while ((next_file = readdir(theFolder)) != nullptr) {

auth/src/desktop/secure/user_secure_internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class UserSecureInternal {
4040

4141
// Delete all user data.
4242
virtual void DeleteAllData() = 0;
43+
44+
// By default does nothing, but for subclasses this enables running in test
45+
// mode (needed on some platforms).
46+
static void EnableTestMode() {}
4347
};
4448

4549
} // namespace secure

auth/src/desktop/secure/user_secure_manager.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
#define USER_SECURE_TYPE UserSecureWindowsInternal
2727

2828
#elif defined(TARGET_OS_OSX) && TARGET_OS_OSX
29-
#include "auth/src/desktop/secure/user_secure_fake_internal.h"
30-
#define USER_SECURE_TYPE UserSecureFakeInternal
29+
#include "auth/src/desktop/secure/user_secure_darwin_internal.h"
30+
#define USER_SECURE_TYPE UserSecureDarwinInternal
3131

3232
#elif defined(__linux__)
3333
#include "auth/src/desktop/secure/user_secure_fake_internal.h"

0 commit comments

Comments
 (0)