Remove depprecated methods in AppleKeychain.
BUG=595468 Review-Url: https://codereview.chromium.org/2932473004 Cr-Commit-Position: refs/heads/master@{#478050}
This commit is contained in:
@@ -54,50 +54,7 @@ class CRYPTO_EXPORT AppleKeychain {
|
|||||||
SecKeychainItemRef* itemRef) const;
|
SecKeychainItemRef* itemRef) const;
|
||||||
|
|
||||||
#if !defined(OS_IOS)
|
#if !defined(OS_IOS)
|
||||||
virtual OSStatus ItemCopyAttributesAndData(
|
|
||||||
SecKeychainItemRef itemRef,
|
|
||||||
SecKeychainAttributeInfo* info,
|
|
||||||
SecItemClass* itemClass,
|
|
||||||
SecKeychainAttributeList** attrList,
|
|
||||||
UInt32* length,
|
|
||||||
void** outData) const;
|
|
||||||
|
|
||||||
virtual OSStatus ItemModifyAttributesAndData(
|
|
||||||
SecKeychainItemRef itemRef,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
UInt32 length,
|
|
||||||
const void* data) const;
|
|
||||||
|
|
||||||
virtual OSStatus ItemFreeAttributesAndData(SecKeychainAttributeList* attrList,
|
|
||||||
void* data) const;
|
|
||||||
|
|
||||||
virtual OSStatus ItemDelete(SecKeychainItemRef itemRef) const;
|
virtual OSStatus ItemDelete(SecKeychainItemRef itemRef) const;
|
||||||
|
|
||||||
virtual OSStatus SearchCreateFromAttributes(
|
|
||||||
CFTypeRef keychainOrArray,
|
|
||||||
SecItemClass itemClass,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
SecKeychainSearchRef* searchRef) const;
|
|
||||||
|
|
||||||
virtual OSStatus SearchCopyNext(SecKeychainSearchRef searchRef,
|
|
||||||
SecKeychainItemRef* itemRef) const;
|
|
||||||
|
|
||||||
virtual OSStatus AddInternetPassword(SecKeychainRef keychain,
|
|
||||||
UInt32 serverNameLength,
|
|
||||||
const char* serverName,
|
|
||||||
UInt32 securityDomainLength,
|
|
||||||
const char* securityDomain,
|
|
||||||
UInt32 accountNameLength,
|
|
||||||
const char* accountName,
|
|
||||||
UInt32 pathLength, const char* path,
|
|
||||||
UInt16 port, SecProtocolType protocol,
|
|
||||||
SecAuthenticationType authenticationType,
|
|
||||||
UInt32 passwordLength,
|
|
||||||
const void* passwordData,
|
|
||||||
SecKeychainItemRef* itemRef) const;
|
|
||||||
|
|
||||||
// Calls CFRelease on the given ref, after checking that |ref| is non-NULL.
|
|
||||||
virtual void Free(CFTypeRef ref) const;
|
|
||||||
#endif // !defined(OS_IOS)
|
#endif // !defined(OS_IOS)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@@ -15,93 +15,11 @@ AppleKeychain::AppleKeychain() {}
|
|||||||
|
|
||||||
AppleKeychain::~AppleKeychain() {}
|
AppleKeychain::~AppleKeychain() {}
|
||||||
|
|
||||||
OSStatus AppleKeychain::ItemCopyAttributesAndData(
|
|
||||||
SecKeychainItemRef itemRef,
|
|
||||||
SecKeychainAttributeInfo* info,
|
|
||||||
SecItemClass* itemClass,
|
|
||||||
SecKeychainAttributeList** attrList,
|
|
||||||
UInt32* length,
|
|
||||||
void** outData) const {
|
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
|
||||||
return SecKeychainItemCopyAttributesAndData(itemRef, info, itemClass,
|
|
||||||
attrList, length, outData);
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AppleKeychain::ItemModifyAttributesAndData(
|
|
||||||
SecKeychainItemRef itemRef,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
UInt32 length,
|
|
||||||
const void* data) const {
|
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
|
||||||
return SecKeychainItemModifyAttributesAndData(itemRef, attrList, length,
|
|
||||||
data);
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AppleKeychain::ItemFreeAttributesAndData(
|
|
||||||
SecKeychainAttributeList* attrList,
|
|
||||||
void* data) const {
|
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
|
||||||
return SecKeychainItemFreeAttributesAndData(attrList, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const {
|
OSStatus AppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const {
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||||
return SecKeychainItemDelete(itemRef);
|
return SecKeychainItemDelete(itemRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
OSStatus AppleKeychain::SearchCreateFromAttributes(
|
|
||||||
CFTypeRef keychainOrArray,
|
|
||||||
SecItemClass itemClass,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
SecKeychainSearchRef* searchRef) const {
|
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
|
||||||
// Eventually, this deprecated method should be removed entirely.
|
|
||||||
// https://crbug.com/595468
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
return SecKeychainSearchCreateFromAttributes(keychainOrArray, itemClass,
|
|
||||||
attrList, searchRef);
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AppleKeychain::SearchCopyNext(SecKeychainSearchRef searchRef,
|
|
||||||
SecKeychainItemRef* itemRef) const {
|
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
|
||||||
// Eventually, this deprecated method should be removed entirely.
|
|
||||||
// https://crbug.com/595468
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
return SecKeychainSearchCopyNext(searchRef, itemRef);
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AppleKeychain::AddInternetPassword(
|
|
||||||
SecKeychainRef keychain,
|
|
||||||
UInt32 serverNameLength,
|
|
||||||
const char* serverName,
|
|
||||||
UInt32 securityDomainLength,
|
|
||||||
const char* securityDomain,
|
|
||||||
UInt32 accountNameLength,
|
|
||||||
const char* accountName,
|
|
||||||
UInt32 pathLength,
|
|
||||||
const char* path,
|
|
||||||
UInt16 port,
|
|
||||||
SecProtocolType protocol,
|
|
||||||
SecAuthenticationType authenticationType,
|
|
||||||
UInt32 passwordLength,
|
|
||||||
const void* passwordData,
|
|
||||||
SecKeychainItemRef* itemRef) const {
|
|
||||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
|
||||||
return SecKeychainAddInternetPassword(keychain,
|
|
||||||
serverNameLength, serverName,
|
|
||||||
securityDomainLength, securityDomain,
|
|
||||||
accountNameLength, accountName,
|
|
||||||
pathLength, path,
|
|
||||||
port, protocol, authenticationType,
|
|
||||||
passwordLength, passwordData,
|
|
||||||
itemRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AppleKeychain::FindGenericPassword(CFTypeRef keychainOrArray,
|
OSStatus AppleKeychain::FindGenericPassword(CFTypeRef keychainOrArray,
|
||||||
UInt32 serviceNameLength,
|
UInt32 serviceNameLength,
|
||||||
const char* serviceName,
|
const char* serviceName,
|
||||||
@@ -146,9 +64,4 @@ OSStatus AppleKeychain::AddGenericPassword(SecKeychainRef keychain,
|
|||||||
itemRef);
|
itemRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppleKeychain::Free(CFTypeRef ref) const {
|
|
||||||
if (ref)
|
|
||||||
CFRelease(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace crypto
|
} // namespace crypto
|
||||||
|
@@ -72,9 +72,6 @@ OSStatus MockAppleKeychain::AddGenericPassword(
|
|||||||
|
|
||||||
DCHECK_GT(passwordLength, 0U);
|
DCHECK_GT(passwordLength, 0U);
|
||||||
DCHECK(passwordData);
|
DCHECK(passwordData);
|
||||||
add_generic_password_ =
|
|
||||||
std::string(const_cast<char*>(static_cast<const char*>(passwordData)),
|
|
||||||
passwordLength);
|
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,9 +19,7 @@
|
|||||||
namespace crypto {
|
namespace crypto {
|
||||||
|
|
||||||
// Mock Keychain wrapper for testing code that interacts with the OS X
|
// Mock Keychain wrapper for testing code that interacts with the OS X
|
||||||
// Keychain. Implemented by storing SecKeychainAttributeList and
|
// Keychain.
|
||||||
// KeychainPasswordData values in separate mutable containers and
|
|
||||||
// mapping them to integer keys.
|
|
||||||
//
|
//
|
||||||
// Note that "const" is pretty much meaningless for this class; the const-ness
|
// Note that "const" is pretty much meaningless for this class; the const-ness
|
||||||
// of AppleKeychain doesn't apply to the actual keychain data, so all of the
|
// of AppleKeychain doesn't apply to the actual keychain data, so all of the
|
||||||
@@ -56,71 +54,7 @@ class CRYPTO_EXPORT MockAppleKeychain : public AppleKeychain {
|
|||||||
std::string GetEncryptionPassword() const;
|
std::string GetEncryptionPassword() const;
|
||||||
|
|
||||||
#if !defined(OS_IOS)
|
#if !defined(OS_IOS)
|
||||||
OSStatus ItemCopyAttributesAndData(SecKeychainItemRef itemRef,
|
|
||||||
SecKeychainAttributeInfo* info,
|
|
||||||
SecItemClass* itemClass,
|
|
||||||
SecKeychainAttributeList** attrList,
|
|
||||||
UInt32* length,
|
|
||||||
void** outData) const override;
|
|
||||||
// Pass "fail_me" as the data to get errSecAuthFailed.
|
|
||||||
OSStatus ItemModifyAttributesAndData(SecKeychainItemRef itemRef,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
UInt32 length,
|
|
||||||
const void* data) const override;
|
|
||||||
OSStatus ItemFreeAttributesAndData(SecKeychainAttributeList* attrList,
|
|
||||||
void* data) const override;
|
|
||||||
OSStatus ItemDelete(SecKeychainItemRef itemRef) const override;
|
OSStatus ItemDelete(SecKeychainItemRef itemRef) const override;
|
||||||
OSStatus SearchCreateFromAttributes(
|
|
||||||
CFTypeRef keychainOrArray,
|
|
||||||
SecItemClass itemClass,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
SecKeychainSearchRef* searchRef) const override;
|
|
||||||
OSStatus SearchCopyNext(SecKeychainSearchRef searchRef,
|
|
||||||
SecKeychainItemRef* itemRef) const override;
|
|
||||||
// Pass "some.domain.com" as the serverName to get errSecDuplicateItem.
|
|
||||||
OSStatus AddInternetPassword(SecKeychainRef keychain,
|
|
||||||
UInt32 serverNameLength,
|
|
||||||
const char* serverName,
|
|
||||||
UInt32 securityDomainLength,
|
|
||||||
const char* securityDomain,
|
|
||||||
UInt32 accountNameLength,
|
|
||||||
const char* accountName,
|
|
||||||
UInt32 pathLength,
|
|
||||||
const char* path,
|
|
||||||
UInt16 port,
|
|
||||||
SecProtocolType protocol,
|
|
||||||
SecAuthenticationType authenticationType,
|
|
||||||
UInt32 passwordLength,
|
|
||||||
const void* passwordData,
|
|
||||||
SecKeychainItemRef* itemRef) const override;
|
|
||||||
void Free(CFTypeRef ref) const override;
|
|
||||||
|
|
||||||
// Return the counts of objects returned by Create/Copy functions but never
|
|
||||||
// Free'd as they should have been.
|
|
||||||
int UnfreedSearchCount() const;
|
|
||||||
int UnfreedKeychainItemCount() const;
|
|
||||||
int UnfreedAttributeDataCount() const;
|
|
||||||
|
|
||||||
// Returns true if all items added with AddInternetPassword have a creator
|
|
||||||
// code set.
|
|
||||||
bool CreatorCodesSetForAddedItems() const;
|
|
||||||
|
|
||||||
struct KeychainTestData {
|
|
||||||
const SecAuthenticationType auth_type;
|
|
||||||
const char* server;
|
|
||||||
const SecProtocolType protocol;
|
|
||||||
const char* path;
|
|
||||||
const UInt32 port;
|
|
||||||
const char* security_domain;
|
|
||||||
const char* creation_date;
|
|
||||||
const char* username;
|
|
||||||
const char* password;
|
|
||||||
const bool negative_item;
|
|
||||||
};
|
|
||||||
// Adds a keychain item with the given info to the test set.
|
|
||||||
void AddTestItem(const KeychainTestData& item_data);
|
|
||||||
|
|
||||||
void set_locked(bool locked) { locked_ = locked; }
|
|
||||||
#endif // !defined(OS_IOS)
|
#endif // !defined(OS_IOS)
|
||||||
|
|
||||||
// |FindGenericPassword()| can return different results depending on user
|
// |FindGenericPassword()| can return different results depending on user
|
||||||
@@ -135,109 +69,10 @@ class CRYPTO_EXPORT MockAppleKeychain : public AppleKeychain {
|
|||||||
// Returns the true if |AddGenericPassword()| was called.
|
// Returns the true if |AddGenericPassword()| was called.
|
||||||
bool called_add_generic() const { return called_add_generic_; }
|
bool called_add_generic() const { return called_add_generic_; }
|
||||||
|
|
||||||
// Returns the value of the password set when |AddGenericPassword()| was
|
|
||||||
// called.
|
|
||||||
std::string add_generic_password() const { return add_generic_password_; }
|
|
||||||
|
|
||||||
// Returns the number of allocations - deallocations for password data.
|
// Returns the number of allocations - deallocations for password data.
|
||||||
int password_data_count() const { return password_data_count_; }
|
int password_data_count() const { return password_data_count_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Type used for the keys in the std::map(s) and MockAppleKeychain items.
|
|
||||||
typedef uintptr_t MockKeychainItemType;
|
|
||||||
|
|
||||||
// Type of the map holding the mock keychain attributes.
|
|
||||||
typedef std::map<MockKeychainItemType, SecKeychainAttributeList>
|
|
||||||
MockKeychainAttributesMap;
|
|
||||||
|
|
||||||
#if !defined(OS_IOS)
|
|
||||||
// Returns true if the keychain already contains a password that matches the
|
|
||||||
// attributes provided.
|
|
||||||
bool AlreadyContainsInternetPassword(
|
|
||||||
UInt32 serverNameLength,
|
|
||||||
const char* serverName,
|
|
||||||
UInt32 securityDomainLength,
|
|
||||||
const char* securityDomain,
|
|
||||||
UInt32 accountNameLength,
|
|
||||||
const char* accountName,
|
|
||||||
UInt32 pathLength,
|
|
||||||
const char* path,
|
|
||||||
UInt16 port,
|
|
||||||
SecProtocolType protocol,
|
|
||||||
SecAuthenticationType authenticationType) const;
|
|
||||||
// Initializes storage for keychain data at |key|.
|
|
||||||
void InitializeKeychainData(MockKeychainItemType key) const;
|
|
||||||
// Sets the data and length of |tag| in the item-th test item.
|
|
||||||
void SetTestDataBytes(
|
|
||||||
MockKeychainItemType item,
|
|
||||||
UInt32 tag,
|
|
||||||
const void* data,
|
|
||||||
size_t length);
|
|
||||||
// Sets the data and length of |tag| in the item-th test item based on
|
|
||||||
// |value|. The null-terminator will not be included; the Keychain Services
|
|
||||||
// docs don't indicate whether it is or not, so clients should not assume
|
|
||||||
// that it will be.
|
|
||||||
void SetTestDataString(MockKeychainItemType item,
|
|
||||||
UInt32 tag,
|
|
||||||
const char* value);
|
|
||||||
// Sets the data of the corresponding attribute of the item-th test item to
|
|
||||||
// |value|. Assumes that the space has alread been allocated, and the length
|
|
||||||
// set.
|
|
||||||
void SetTestDataPort(MockKeychainItemType item, UInt32 value);
|
|
||||||
void SetTestDataProtocol(MockKeychainItemType item, SecProtocolType value);
|
|
||||||
void SetTestDataAuthType(MockKeychainItemType item,
|
|
||||||
SecAuthenticationType value);
|
|
||||||
void SetTestDataNegativeItem(MockKeychainItemType item, Boolean value);
|
|
||||||
void SetTestDataCreator(MockKeychainItemType item, OSType value);
|
|
||||||
// Sets the password data and length for the item-th test item.
|
|
||||||
void SetTestDataPasswordBytes(MockKeychainItemType item,
|
|
||||||
const void* data,
|
|
||||||
size_t length);
|
|
||||||
// Sets the password for the item-th test item. As with SetTestDataString,
|
|
||||||
// the data will not be null-terminated.
|
|
||||||
void SetTestDataPasswordString(MockKeychainItemType item, const char* value);
|
|
||||||
|
|
||||||
// Returns the address of the attribute in attribute_list with tag |tag|.
|
|
||||||
static SecKeychainAttribute* AttributeWithTag(
|
|
||||||
const SecKeychainAttributeList& attribute_list,
|
|
||||||
UInt32 tag);
|
|
||||||
|
|
||||||
static const SecKeychainSearchRef kDummySearchRef;
|
|
||||||
|
|
||||||
// Simulates the state when the user refuses to unclock the Keychain.
|
|
||||||
// If true, reading and modifying a password value result in errSecAuthFailed.
|
|
||||||
bool locked_;
|
|
||||||
|
|
||||||
typedef struct KeychainPasswordData {
|
|
||||||
KeychainPasswordData() : data(nullptr), length(0) {}
|
|
||||||
void* data;
|
|
||||||
UInt32 length;
|
|
||||||
} KeychainPasswordData;
|
|
||||||
|
|
||||||
// Mutable because the MockAppleKeychain API requires its internal keychain
|
|
||||||
// storage to be modifiable by users of this class.
|
|
||||||
mutable MockKeychainAttributesMap keychain_attr_list_;
|
|
||||||
mutable std::map<MockKeychainItemType,
|
|
||||||
KeychainPasswordData> keychain_data_;
|
|
||||||
mutable MockKeychainItemType next_item_key_;
|
|
||||||
|
|
||||||
// Tracks the items that should be returned in subsequent calls to
|
|
||||||
// SearchCopyNext, based on the last call to SearchCreateFromAttributes.
|
|
||||||
// We can't handle multiple active searches, since we don't track the search
|
|
||||||
// ref we return, but we don't need to for our mocking.
|
|
||||||
mutable std::vector<MockKeychainItemType> remaining_search_results_;
|
|
||||||
|
|
||||||
// Track copies and releases to make sure they balance. Really these should
|
|
||||||
// be maps to track per item, but this should be good enough to catch
|
|
||||||
// real mistakes.
|
|
||||||
mutable int search_copy_count_;
|
|
||||||
mutable int keychain_item_copy_count_;
|
|
||||||
mutable int attribute_data_copy_count_;
|
|
||||||
|
|
||||||
// Tracks which items (by key) were added with AddInternetPassword.
|
|
||||||
mutable std::set<MockKeychainItemType> added_via_api_;
|
|
||||||
#endif // !defined(OS_IOS)
|
|
||||||
|
|
||||||
// Result code for the |FindGenericPassword()| method.
|
// Result code for the |FindGenericPassword()| method.
|
||||||
OSStatus find_generic_result_;
|
OSStatus find_generic_result_;
|
||||||
|
|
||||||
@@ -248,8 +83,7 @@ class CRYPTO_EXPORT MockAppleKeychain : public AppleKeychain {
|
|||||||
// and |ItemFreeContent|.
|
// and |ItemFreeContent|.
|
||||||
mutable int password_data_count_;
|
mutable int password_data_count_;
|
||||||
|
|
||||||
// Records the password being set when |AddGenericPassword()| gets called.
|
DISALLOW_COPY_AND_ASSIGN(MockAppleKeychain);
|
||||||
mutable std::string add_generic_password_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace crypto
|
} // namespace crypto
|
||||||
|
@@ -11,512 +11,15 @@
|
|||||||
|
|
||||||
namespace crypto {
|
namespace crypto {
|
||||||
|
|
||||||
// static
|
|
||||||
const SecKeychainSearchRef MockAppleKeychain::kDummySearchRef =
|
|
||||||
reinterpret_cast<SecKeychainSearchRef>(1000);
|
|
||||||
|
|
||||||
MockAppleKeychain::MockAppleKeychain()
|
MockAppleKeychain::MockAppleKeychain()
|
||||||
: locked_(false),
|
: find_generic_result_(noErr),
|
||||||
next_item_key_(0),
|
|
||||||
search_copy_count_(0),
|
|
||||||
keychain_item_copy_count_(0),
|
|
||||||
attribute_data_copy_count_(0),
|
|
||||||
find_generic_result_(noErr),
|
|
||||||
called_add_generic_(false),
|
called_add_generic_(false),
|
||||||
password_data_count_(0) {}
|
password_data_count_(0) {}
|
||||||
|
|
||||||
void MockAppleKeychain::InitializeKeychainData(MockKeychainItemType key) const {
|
MockAppleKeychain::~MockAppleKeychain() {}
|
||||||
UInt32 tags[] = { kSecAccountItemAttr,
|
|
||||||
kSecServerItemAttr,
|
|
||||||
kSecPortItemAttr,
|
|
||||||
kSecPathItemAttr,
|
|
||||||
kSecProtocolItemAttr,
|
|
||||||
kSecAuthenticationTypeItemAttr,
|
|
||||||
kSecSecurityDomainItemAttr,
|
|
||||||
kSecCreationDateItemAttr,
|
|
||||||
kSecNegativeItemAttr,
|
|
||||||
kSecCreatorItemAttr };
|
|
||||||
keychain_attr_list_[key] = SecKeychainAttributeList();
|
|
||||||
keychain_data_[key] = KeychainPasswordData();
|
|
||||||
keychain_attr_list_[key].count = arraysize(tags);
|
|
||||||
keychain_attr_list_[key].attr = static_cast<SecKeychainAttribute*>(
|
|
||||||
calloc(keychain_attr_list_[key].count, sizeof(SecKeychainAttribute)));
|
|
||||||
for (unsigned int i = 0; i < keychain_attr_list_[key].count; ++i) {
|
|
||||||
keychain_attr_list_[key].attr[i].tag = tags[i];
|
|
||||||
size_t data_size = 0;
|
|
||||||
switch (tags[i]) {
|
|
||||||
case kSecPortItemAttr:
|
|
||||||
data_size = sizeof(UInt32);
|
|
||||||
break;
|
|
||||||
case kSecProtocolItemAttr:
|
|
||||||
data_size = sizeof(SecProtocolType);
|
|
||||||
break;
|
|
||||||
case kSecAuthenticationTypeItemAttr:
|
|
||||||
data_size = sizeof(SecAuthenticationType);
|
|
||||||
break;
|
|
||||||
case kSecNegativeItemAttr:
|
|
||||||
data_size = sizeof(Boolean);
|
|
||||||
break;
|
|
||||||
case kSecCreatorItemAttr:
|
|
||||||
data_size = sizeof(OSType);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (data_size > 0) {
|
|
||||||
keychain_attr_list_[key].attr[i].length = data_size;
|
|
||||||
keychain_attr_list_[key].attr[i].data = calloc(1, data_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MockAppleKeychain::~MockAppleKeychain() {
|
|
||||||
for (MockKeychainAttributesMap::iterator it = keychain_attr_list_.begin();
|
|
||||||
it != keychain_attr_list_.end();
|
|
||||||
++it) {
|
|
||||||
for (unsigned int i = 0; i < it->second.count; ++i) {
|
|
||||||
if (it->second.attr[i].data)
|
|
||||||
free(it->second.attr[i].data);
|
|
||||||
}
|
|
||||||
free(it->second.attr);
|
|
||||||
if (keychain_data_[it->first].data)
|
|
||||||
free(keychain_data_[it->first].data);
|
|
||||||
}
|
|
||||||
keychain_attr_list_.clear();
|
|
||||||
keychain_data_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
SecKeychainAttribute* MockAppleKeychain::AttributeWithTag(
|
|
||||||
const SecKeychainAttributeList& attribute_list,
|
|
||||||
UInt32 tag) {
|
|
||||||
int attribute_index = -1;
|
|
||||||
for (unsigned int i = 0; i < attribute_list.count; ++i) {
|
|
||||||
if (attribute_list.attr[i].tag == tag) {
|
|
||||||
attribute_index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attribute_index == -1) {
|
|
||||||
NOTREACHED() << "Unsupported attribute: " << tag;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return &(attribute_list.attr[attribute_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataBytes(MockKeychainItemType item,
|
|
||||||
UInt32 tag,
|
|
||||||
const void* data,
|
|
||||||
size_t length) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item],
|
|
||||||
tag);
|
|
||||||
attribute->length = length;
|
|
||||||
if (length > 0) {
|
|
||||||
if (attribute->data)
|
|
||||||
free(attribute->data);
|
|
||||||
attribute->data = malloc(length);
|
|
||||||
CHECK(attribute->data);
|
|
||||||
memcpy(attribute->data, data, length);
|
|
||||||
} else {
|
|
||||||
attribute->data = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataString(MockKeychainItemType item,
|
|
||||||
UInt32 tag,
|
|
||||||
const char* value) {
|
|
||||||
SetTestDataBytes(item, tag, value, value ? strlen(value) : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataPort(MockKeychainItemType item,
|
|
||||||
UInt32 value) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item],
|
|
||||||
kSecPortItemAttr);
|
|
||||||
UInt32* data = static_cast<UInt32*>(attribute->data);
|
|
||||||
*data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataProtocol(MockKeychainItemType item,
|
|
||||||
SecProtocolType value) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item],
|
|
||||||
kSecProtocolItemAttr);
|
|
||||||
SecProtocolType* data = static_cast<SecProtocolType*>(attribute->data);
|
|
||||||
*data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataAuthType(MockKeychainItemType item,
|
|
||||||
SecAuthenticationType value) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(
|
|
||||||
keychain_attr_list_[item], kSecAuthenticationTypeItemAttr);
|
|
||||||
SecAuthenticationType* data = static_cast<SecAuthenticationType*>(
|
|
||||||
attribute->data);
|
|
||||||
*data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataNegativeItem(MockKeychainItemType item,
|
|
||||||
Boolean value) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item],
|
|
||||||
kSecNegativeItemAttr);
|
|
||||||
Boolean* data = static_cast<Boolean*>(attribute->data);
|
|
||||||
*data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataCreator(MockKeychainItemType item,
|
|
||||||
OSType value) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item],
|
|
||||||
kSecCreatorItemAttr);
|
|
||||||
OSType* data = static_cast<OSType*>(attribute->data);
|
|
||||||
*data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataPasswordBytes(MockKeychainItemType item,
|
|
||||||
const void* data,
|
|
||||||
size_t length) {
|
|
||||||
keychain_data_[item].length = length;
|
|
||||||
if (length > 0) {
|
|
||||||
if (keychain_data_[item].data)
|
|
||||||
free(keychain_data_[item].data);
|
|
||||||
keychain_data_[item].data = malloc(length);
|
|
||||||
memcpy(keychain_data_[item].data, data, length);
|
|
||||||
} else {
|
|
||||||
keychain_data_[item].data = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::SetTestDataPasswordString(MockKeychainItemType item,
|
|
||||||
const char* value) {
|
|
||||||
SetTestDataPasswordBytes(item, value, value ? strlen(value) : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::ItemCopyAttributesAndData(
|
|
||||||
SecKeychainItemRef itemRef,
|
|
||||||
SecKeychainAttributeInfo* info,
|
|
||||||
SecItemClass* itemClass,
|
|
||||||
SecKeychainAttributeList** attrList,
|
|
||||||
UInt32* length,
|
|
||||||
void** outData) const {
|
|
||||||
DCHECK(itemRef);
|
|
||||||
MockKeychainItemType key =
|
|
||||||
reinterpret_cast<MockKeychainItemType>(itemRef) - 1;
|
|
||||||
if (keychain_attr_list_.find(key) == keychain_attr_list_.end())
|
|
||||||
return errSecInvalidItemRef;
|
|
||||||
|
|
||||||
DCHECK(!itemClass); // itemClass not implemented in the Mock.
|
|
||||||
if (locked_ && outData)
|
|
||||||
return errSecAuthFailed;
|
|
||||||
|
|
||||||
if (attrList)
|
|
||||||
*attrList = &(keychain_attr_list_[key]);
|
|
||||||
if (outData) {
|
|
||||||
*outData = keychain_data_[key].data;
|
|
||||||
DCHECK(length);
|
|
||||||
*length = keychain_data_[key].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
++attribute_data_copy_count_;
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::ItemModifyAttributesAndData(
|
|
||||||
SecKeychainItemRef itemRef,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
UInt32 length,
|
|
||||||
const void* data) const {
|
|
||||||
DCHECK(itemRef);
|
|
||||||
if (locked_)
|
|
||||||
return errSecAuthFailed;
|
|
||||||
const char* fail_trigger = "fail_me";
|
|
||||||
if (length == strlen(fail_trigger) &&
|
|
||||||
memcmp(data, fail_trigger, length) == 0) {
|
|
||||||
return errSecAuthFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
MockKeychainItemType key =
|
|
||||||
reinterpret_cast<MockKeychainItemType>(itemRef) - 1;
|
|
||||||
if (keychain_attr_list_.find(key) == keychain_attr_list_.end())
|
|
||||||
return errSecInvalidItemRef;
|
|
||||||
|
|
||||||
MockAppleKeychain* mutable_this = const_cast<MockAppleKeychain*>(this);
|
|
||||||
if (attrList) {
|
|
||||||
for (UInt32 change_attr = 0; change_attr < attrList->count; ++change_attr) {
|
|
||||||
if (attrList->attr[change_attr].tag == kSecCreatorItemAttr) {
|
|
||||||
void* data = attrList->attr[change_attr].data;
|
|
||||||
mutable_this->SetTestDataCreator(key, *(static_cast<OSType*>(data)));
|
|
||||||
} else {
|
|
||||||
NOTIMPLEMENTED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data)
|
|
||||||
mutable_this->SetTestDataPasswordBytes(key, data, length);
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::ItemFreeAttributesAndData(
|
|
||||||
SecKeychainAttributeList* attrList,
|
|
||||||
void* data) const {
|
|
||||||
--attribute_data_copy_count_;
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const {
|
OSStatus MockAppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const {
|
||||||
if (locked_)
|
|
||||||
return errSecAuthFailed;
|
|
||||||
MockKeychainItemType key =
|
|
||||||
reinterpret_cast<MockKeychainItemType>(itemRef) - 1;
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < keychain_attr_list_[key].count; ++i) {
|
|
||||||
if (keychain_attr_list_[key].attr[i].data)
|
|
||||||
free(keychain_attr_list_[key].attr[i].data);
|
|
||||||
}
|
|
||||||
free(keychain_attr_list_[key].attr);
|
|
||||||
if (keychain_data_[key].data)
|
|
||||||
free(keychain_data_[key].data);
|
|
||||||
|
|
||||||
keychain_attr_list_.erase(key);
|
|
||||||
keychain_data_.erase(key);
|
|
||||||
added_via_api_.erase(key);
|
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::SearchCreateFromAttributes(
|
|
||||||
CFTypeRef keychainOrArray,
|
|
||||||
SecItemClass itemClass,
|
|
||||||
const SecKeychainAttributeList* attrList,
|
|
||||||
SecKeychainSearchRef* searchRef) const {
|
|
||||||
// Figure out which of our mock items matches, and set up the array we'll use
|
|
||||||
// to generate results out of SearchCopyNext.
|
|
||||||
remaining_search_results_.clear();
|
|
||||||
for (MockKeychainAttributesMap::const_iterator it =
|
|
||||||
keychain_attr_list_.begin();
|
|
||||||
it != keychain_attr_list_.end();
|
|
||||||
++it) {
|
|
||||||
bool mock_item_matches = true;
|
|
||||||
for (UInt32 search_attr = 0; search_attr < attrList->count; ++search_attr) {
|
|
||||||
SecKeychainAttribute* mock_attribute =
|
|
||||||
AttributeWithTag(it->second, attrList->attr[search_attr].tag);
|
|
||||||
if (mock_attribute->length != attrList->attr[search_attr].length ||
|
|
||||||
memcmp(mock_attribute->data, attrList->attr[search_attr].data,
|
|
||||||
attrList->attr[search_attr].length) != 0) {
|
|
||||||
mock_item_matches = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mock_item_matches)
|
|
||||||
remaining_search_results_.push_back(it->first);
|
|
||||||
}
|
|
||||||
|
|
||||||
DCHECK(searchRef);
|
|
||||||
*searchRef = kDummySearchRef;
|
|
||||||
++search_copy_count_;
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MockAppleKeychain::AlreadyContainsInternetPassword(
|
|
||||||
UInt32 serverNameLength,
|
|
||||||
const char* serverName,
|
|
||||||
UInt32 securityDomainLength,
|
|
||||||
const char* securityDomain,
|
|
||||||
UInt32 accountNameLength,
|
|
||||||
const char* accountName,
|
|
||||||
UInt32 pathLength,
|
|
||||||
const char* path,
|
|
||||||
UInt16 port,
|
|
||||||
SecProtocolType protocol,
|
|
||||||
SecAuthenticationType authenticationType) const {
|
|
||||||
for (MockKeychainAttributesMap::const_iterator it =
|
|
||||||
keychain_attr_list_.begin();
|
|
||||||
it != keychain_attr_list_.end();
|
|
||||||
++it) {
|
|
||||||
SecKeychainAttribute* attribute;
|
|
||||||
attribute = AttributeWithTag(it->second, kSecServerItemAttr);
|
|
||||||
if ((attribute->length != serverNameLength) ||
|
|
||||||
(attribute->data == NULL && *serverName != '\0') ||
|
|
||||||
(attribute->data != NULL && *serverName == '\0') ||
|
|
||||||
strncmp(serverName,
|
|
||||||
(const char*) attribute->data,
|
|
||||||
serverNameLength) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attribute = AttributeWithTag(it->second, kSecSecurityDomainItemAttr);
|
|
||||||
if ((attribute->length != securityDomainLength) ||
|
|
||||||
(attribute->data == NULL && *securityDomain != '\0') ||
|
|
||||||
(attribute->data != NULL && *securityDomain == '\0') ||
|
|
||||||
strncmp(securityDomain,
|
|
||||||
(const char*) attribute->data,
|
|
||||||
securityDomainLength) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attribute = AttributeWithTag(it->second, kSecAccountItemAttr);
|
|
||||||
if ((attribute->length != accountNameLength) ||
|
|
||||||
(attribute->data == NULL && *accountName != '\0') ||
|
|
||||||
(attribute->data != NULL && *accountName == '\0') ||
|
|
||||||
strncmp(accountName,
|
|
||||||
(const char*) attribute->data,
|
|
||||||
accountNameLength) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attribute = AttributeWithTag(it->second, kSecPathItemAttr);
|
|
||||||
if ((attribute->length != pathLength) ||
|
|
||||||
(attribute->data == NULL && *path != '\0') ||
|
|
||||||
(attribute->data != NULL && *path == '\0') ||
|
|
||||||
strncmp(path,
|
|
||||||
(const char*) attribute->data,
|
|
||||||
pathLength) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attribute = AttributeWithTag(it->second, kSecPortItemAttr);
|
|
||||||
if ((attribute->data == NULL) ||
|
|
||||||
(port != *(static_cast<UInt32*>(attribute->data)))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attribute = AttributeWithTag(it->second, kSecProtocolItemAttr);
|
|
||||||
if ((attribute->data == NULL) ||
|
|
||||||
(protocol != *(static_cast<SecProtocolType*>(attribute->data)))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
attribute = AttributeWithTag(it->second, kSecAuthenticationTypeItemAttr);
|
|
||||||
if ((attribute->data == NULL) ||
|
|
||||||
(authenticationType !=
|
|
||||||
*(static_cast<SecAuthenticationType*>(attribute->data)))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// The keychain already has this item, since all fields other than the
|
|
||||||
// password match.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::AddInternetPassword(
|
|
||||||
SecKeychainRef keychain,
|
|
||||||
UInt32 serverNameLength,
|
|
||||||
const char* serverName,
|
|
||||||
UInt32 securityDomainLength,
|
|
||||||
const char* securityDomain,
|
|
||||||
UInt32 accountNameLength,
|
|
||||||
const char* accountName,
|
|
||||||
UInt32 pathLength,
|
|
||||||
const char* path,
|
|
||||||
UInt16 port,
|
|
||||||
SecProtocolType protocol,
|
|
||||||
SecAuthenticationType authenticationType,
|
|
||||||
UInt32 passwordLength,
|
|
||||||
const void* passwordData,
|
|
||||||
SecKeychainItemRef* itemRef) const {
|
|
||||||
if (locked_)
|
|
||||||
return errSecAuthFailed;
|
|
||||||
|
|
||||||
// Check for the magic duplicate item trigger.
|
|
||||||
if (strcmp(serverName, "some.domain.com") == 0)
|
|
||||||
return errSecDuplicateItem;
|
|
||||||
|
|
||||||
// If the account already exists in the keychain, we don't add it.
|
|
||||||
if (AlreadyContainsInternetPassword(serverNameLength, serverName,
|
|
||||||
securityDomainLength, securityDomain,
|
|
||||||
accountNameLength, accountName,
|
|
||||||
pathLength, path,
|
|
||||||
port, protocol,
|
|
||||||
authenticationType)) {
|
|
||||||
return errSecDuplicateItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick the next unused slot.
|
|
||||||
MockKeychainItemType key = next_item_key_++;
|
|
||||||
|
|
||||||
// Initialize keychain data storage at the target location.
|
|
||||||
InitializeKeychainData(key);
|
|
||||||
|
|
||||||
MockAppleKeychain* mutable_this = const_cast<MockAppleKeychain*>(this);
|
|
||||||
mutable_this->SetTestDataBytes(key, kSecServerItemAttr, serverName,
|
|
||||||
serverNameLength);
|
|
||||||
mutable_this->SetTestDataBytes(key, kSecSecurityDomainItemAttr,
|
|
||||||
securityDomain, securityDomainLength);
|
|
||||||
mutable_this->SetTestDataBytes(key, kSecAccountItemAttr, accountName,
|
|
||||||
accountNameLength);
|
|
||||||
mutable_this->SetTestDataBytes(key, kSecPathItemAttr, path, pathLength);
|
|
||||||
mutable_this->SetTestDataPort(key, port);
|
|
||||||
mutable_this->SetTestDataProtocol(key, protocol);
|
|
||||||
mutable_this->SetTestDataAuthType(key, authenticationType);
|
|
||||||
mutable_this->SetTestDataPasswordBytes(key, passwordData,
|
|
||||||
passwordLength);
|
|
||||||
base::Time::Exploded exploded_time;
|
|
||||||
base::Time::Now().UTCExplode(&exploded_time);
|
|
||||||
char time_string[128];
|
|
||||||
snprintf(time_string, sizeof(time_string), "%04d%02d%02d%02d%02d%02dZ",
|
|
||||||
exploded_time.year, exploded_time.month, exploded_time.day_of_month,
|
|
||||||
exploded_time.hour, exploded_time.minute, exploded_time.second);
|
|
||||||
mutable_this->SetTestDataString(key, kSecCreationDateItemAttr, time_string);
|
|
||||||
|
|
||||||
added_via_api_.insert(key);
|
|
||||||
|
|
||||||
if (itemRef) {
|
|
||||||
*itemRef = reinterpret_cast<SecKeychainItemRef>(key + 1);
|
|
||||||
++keychain_item_copy_count_;
|
|
||||||
}
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus MockAppleKeychain::SearchCopyNext(SecKeychainSearchRef searchRef,
|
|
||||||
SecKeychainItemRef* itemRef) const {
|
|
||||||
if (remaining_search_results_.empty())
|
|
||||||
return errSecItemNotFound;
|
|
||||||
MockKeychainItemType key = remaining_search_results_.front();
|
|
||||||
remaining_search_results_.erase(remaining_search_results_.begin());
|
|
||||||
*itemRef = reinterpret_cast<SecKeychainItemRef>(key + 1);
|
|
||||||
++keychain_item_copy_count_;
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::Free(CFTypeRef ref) const {
|
|
||||||
if (!ref)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (ref == kDummySearchRef) {
|
|
||||||
--search_copy_count_;
|
|
||||||
} else {
|
|
||||||
--keychain_item_copy_count_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int MockAppleKeychain::UnfreedSearchCount() const {
|
|
||||||
return search_copy_count_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MockAppleKeychain::UnfreedKeychainItemCount() const {
|
|
||||||
return keychain_item_copy_count_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MockAppleKeychain::UnfreedAttributeDataCount() const {
|
|
||||||
return attribute_data_copy_count_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MockAppleKeychain::CreatorCodesSetForAddedItems() const {
|
|
||||||
for (std::set<MockKeychainItemType>::const_iterator
|
|
||||||
i = added_via_api_.begin();
|
|
||||||
i != added_via_api_.end();
|
|
||||||
++i) {
|
|
||||||
SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[*i],
|
|
||||||
kSecCreatorItemAttr);
|
|
||||||
OSType* data = static_cast<OSType*>(attribute->data);
|
|
||||||
if (*data == 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MockAppleKeychain::AddTestItem(const KeychainTestData& item_data) {
|
|
||||||
MockKeychainItemType key = next_item_key_++;
|
|
||||||
|
|
||||||
InitializeKeychainData(key);
|
|
||||||
SetTestDataAuthType(key, item_data.auth_type);
|
|
||||||
SetTestDataString(key, kSecServerItemAttr, item_data.server);
|
|
||||||
SetTestDataProtocol(key, item_data.protocol);
|
|
||||||
SetTestDataString(key, kSecPathItemAttr, item_data.path);
|
|
||||||
SetTestDataPort(key, item_data.port);
|
|
||||||
SetTestDataString(key, kSecSecurityDomainItemAttr,
|
|
||||||
item_data.security_domain);
|
|
||||||
SetTestDataString(key, kSecCreationDateItemAttr, item_data.creation_date);
|
|
||||||
SetTestDataString(key, kSecAccountItemAttr, item_data.username);
|
|
||||||
SetTestDataPasswordString(key, item_data.password);
|
|
||||||
SetTestDataNegativeItem(key, item_data.negative_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace crypto
|
} // namespace crypto
|
||||||
|
Reference in New Issue
Block a user