blob: 354a16b2b4160f6ec9f863f0a4e841c84c82c97f [file] [log] [blame]
/**
* Copyright 2017 Google Inc.
*
* 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 "objc/TINKKeysetHandle.h"
#import "objc/TINKAead.h"
#import "objc/TINKKeyTemplate.h"
#import "objc/TINKKeysetReader.h"
#import "objc/aead/TINKAeadInternal.h"
#import "objc/core/TINKKeyTemplate_Internal.h"
#import "objc/core/TINKKeysetReader_Internal.h"
#import "objc/util/TINKErrors.h"
#import "objc/util/TINKStrings.h"
#include <iosfwd>
#include <iostream>
#include <sstream>
#include <string>
#include "absl/strings/string_view.h"
#include "tink/binary_keyset_reader.h"
#include "tink/cleartext_keyset_handle.h"
#include "tink/keyset_handle.h"
#include "tink/util/keyset_util.h"
#include "tink/util/status.h"
#include "proto/tink.pb.h"
static NSString *const kTinkService = @"com.google.crypto.tink";
@implementation TINKKeysetHandle {
std::unique_ptr<crypto::tink::KeysetHandle> _ccKeysetHandle;
}
- (instancetype)initWithCCKeysetHandle:(std::unique_ptr<crypto::tink::KeysetHandle>)ccKeysetHandle {
self = [super init];
if (self) {
_ccKeysetHandle = std::move(ccKeysetHandle);
}
return self;
}
- (void)dealloc {
_ccKeysetHandle.reset();
}
- (instancetype)initWithNoSecretKeyset:(NSData *)keyset error:(NSError **)error {
if (keyset == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INVALID_ARGUMENT, "keyset must be non-nil."));
}
return nil;
}
auto st = crypto::tink::KeysetHandle::ReadNoSecret(std::string(
reinterpret_cast<const char *>(keyset.bytes), static_cast<size_t>(keyset.length)));
if (!st.ok()) {
if (error) {
*error = TINKStatusToError(st.status());
return nil;
}
}
return [self initWithCCKeysetHandle:std::move(st.ValueOrDie())];
}
- (instancetype)initWithKeysetReader:(TINKKeysetReader *)reader
andKey:(id<TINKAead>)aeadKey
error:(NSError **)error {
if (![aeadKey isKindOfClass:[TINKAeadInternal class]]) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INVALID_ARGUMENT, "Invalid instance of TINKAead."));
}
return nil;
}
TINKAeadInternal *aead = aeadKey;
crypto::tink::Aead *ccAead = [aead ccAead];
if (!ccAead) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INVALID_ARGUMENT, "Failed to get C++ Aead instance."));
}
return nil;
}
@synchronized(reader) {
if (reader.used) {
// A reader can only be used once.
if (error) {
*error = TINKStatusToError(
crypto::tink::util::Status(crypto::tink::util::error::RESOURCE_EXHAUSTED,
"A KeysetReader can be used only once."));
}
return nil;
}
reader.used = YES;
}
auto st = crypto::tink::KeysetHandle::Read(reader.ccReader, *ccAead);
if (!st.ok()) {
if (error) {
*error = TINKStatusToError(st.status());
return nil;
}
}
return [self initWithCCKeysetHandle:std::move(st.ValueOrDie())];
}
- (nullable instancetype)initFromKeychainWithName:(NSString *)keysetName error:(NSError **)error {
if (keysetName == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INVALID_ARGUMENT, "keysetName must be non-nil."));
}
return nil;
}
if (self = [super init]) {
NSDictionary *getQuery = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : keysetName,
(__bridge id)kSecAttrService : kTinkService,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue,
};
crypto::tink::util::error::Code errorCode = crypto::tink::util::error::OK;
std::string errorMessage = "";
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)getQuery, &dataTypeRef);
switch (status) {
case errSecSuccess:
// Success, nothing to do.
break;
case errSecItemNotFound:
errorMessage = "A keyset with the given name wasn't found in the keychain.";
errorCode = crypto::tink::util::error::NOT_FOUND;
break;
default:
std::ostringstream oss;
oss << "An error occured while trying to retrieve the keyset from the keychain.";
oss << " Error code: " << status;
errorMessage = oss.str();
errorCode = crypto::tink::util::error::UNKNOWN;
}
if (errorCode != crypto::tink::util::error::OK) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(errorCode, errorMessage));
}
return nil;
}
NSData *keyset = (__bridge NSData *)dataTypeRef;
auto reader = crypto::tink::BinaryKeysetReader::New(absl::string_view(
reinterpret_cast<const char *>(keyset.bytes), static_cast<size_t>(keyset.length)));
if (!reader.ok()) {
if (error) {
*error = TINKStatusToError(reader.status());
}
return nil;
}
auto read_result = crypto::tink::CleartextKeysetHandle::Read(std::move(reader.ValueOrDie()));
if (!read_result.ok()) {
if (error) {
*error = TINKStatusToError(read_result.status());
return nil;
}
}
return [self initWithCCKeysetHandle:std::move(read_result.ValueOrDie())];
}
return nil;
}
+ (BOOL)deleteFromKeychainWithName:(NSString *)keysetName error:(NSError **)error {
if (keysetName == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INVALID_ARGUMENT, "keysetName must be non-nil."));
}
return NO;
}
NSDictionary *attributes = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : keysetName,
(__bridge id)kSecAttrService : kTinkService,
};
OSStatus status = SecItemDelete((CFDictionaryRef)attributes);
if (status != errSecSuccess && status != errSecItemNotFound) {
if (error) {
std::ostringstream oss;
oss << "An error occured while trying to delete the keyset from the keychain.";
oss << " Keychain error code: " << status;
std::string errorMessage = oss.str();
*error = TINKStatusToError(
crypto::tink::util::Status(crypto::tink::util::error::UNKNOWN, errorMessage));
}
return NO;
}
return YES;
}
- (BOOL)writeToKeychainWithName:(NSString *)keysetName
overwrite:(BOOL)overwrite
error:(NSError **)error {
if (keysetName == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INVALID_ARGUMENT, "keysetName must be non-nil."));
}
return NO;
}
auto keyset = crypto::tink::KeysetUtil::GetKeyset(*self.ccKeysetHandle);
std::string serializedKeyset;
if (!keyset.SerializeToString(&serializedKeyset)) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
crypto::tink::util::error::INTERNAL, "Could not serialize C++ KeyTemplate."));
}
return NO;
}
if (overwrite) {
if (![TINKKeysetHandle deleteFromKeychainWithName:keysetName error:error]) {
return NO;
}
}
NSData *keysetData = TINKStringToNSData(serializedKeyset);
NSDictionary *attributes = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : keysetName,
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
(__bridge id)kSecAttrService : kTinkService,
(__bridge id)kSecAttrSynchronizable : (__bridge id)kCFBooleanFalse,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue,
(__bridge id)kSecValueData : keysetData,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
switch (status) {
case errSecSuccess:
return YES;
case errSecDuplicateItem:
if (error) {
std::ostringstream oss;
oss << "A keyset with the same keysetName already exists in the keychain.";
oss << " Please delete it and try again. Keychain error code: " << status;
std::string errorMessage = oss.str();
*error = TINKStatusToError(
crypto::tink::util::Status(crypto::tink::util::error::UNKNOWN, errorMessage));
}
return NO;
default:
if (error) {
std::ostringstream oss;
oss << "An error occured while trying to store the keyset in the keychain.";
oss << " Error code: " << status;
std::string errorMessage = oss.str();
*error = TINKStatusToError(
crypto::tink::util::Status(crypto::tink::util::error::UNKNOWN, errorMessage));
}
return NO;
}
}
- (instancetype)initWithKeyTemplate:(TINKKeyTemplate *)keyTemplate error:(NSError **)error {
auto st = crypto::tink::KeysetHandle::GenerateNew(*(keyTemplate.ccKeyTemplate));
if (!st.ok()) {
if (error) {
*error = TINKStatusToError(st.status());
}
return nil;
}
return [self initWithCCKeysetHandle:std::move(st.ValueOrDie())];
}
- (crypto::tink::KeysetHandle *)ccKeysetHandle {
return _ccKeysetHandle.get();
}
- (void)setCcKeysetHandle:(std::unique_ptr<crypto::tink::KeysetHandle>)handle {
_ccKeysetHandle = std::move(handle);
}
+ (nullable instancetype)publicKeysetHandleWithHandle:(TINKKeysetHandle *)aHandle
error:(NSError **)error {
crypto::tink::KeysetHandle *ccKeysetHandle = aHandle.ccKeysetHandle;
auto status = ccKeysetHandle->GetPublicKeysetHandle();
if (!status.ok()) {
if (error) {
*error = TINKStatusToError(status.status());
}
return nil;
}
return [[TINKKeysetHandle alloc] initWithCCKeysetHandle:std::move(status.ValueOrDie())];
}
@end