blob: 31663ddfebe05bb750299421e041a9b20450248c [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/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "tink/binary_keyset_reader.h"
#include "tink/binary_keyset_writer.h"
#include "tink/cleartext_keyset_handle.h"
#include "tink/keyset_handle.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(absl::StatusCode::kInvalidArgument,
"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.value())];
}
- (instancetype)initWithKeysetReader:(TINKKeysetReader *)reader
andKey:(id<TINKAead>)aeadKey
error:(NSError **)error {
if (![aeadKey isKindOfClass:[TINKAeadInternal class]]) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
"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(absl::StatusCode::kInvalidArgument,
"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(absl::StatusCode::kResourceExhausted,
"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.value())];
}
- (nullable instancetype)initFromKeychainWithName:(NSString *)keysetName error:(NSError **)error {
return [self initFromKeychainWithName:keysetName accessGroup:nil error:error];
}
- (nullable instancetype)initFromKeychainWithName:(NSString *)keysetName
accessGroup:(NSString *)accessGroup
error:(NSError **)error {
if (keysetName == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
"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,
};
if (accessGroup) {
NSMutableDictionary *mutableGetQuery =
[NSMutableDictionary dictionaryWithDictionary:getQuery];
[mutableGetQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
getQuery = [mutableGetQuery copy];
}
absl::StatusCode errorCode = absl::StatusCode::kOk;
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 = absl::StatusCode::kNotFound;
break;
default:
std::ostringstream oss;
oss << "An error occurred while trying to retrieve the keyset from the keychain.";
oss << " Error code: " << status;
errorMessage = oss.str();
errorCode = absl::StatusCode::kUnknown;
}
if (errorCode != absl::StatusCode::kOk) {
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.value()));
if (!read_result.ok()) {
if (error) {
*error = TINKStatusToError(read_result.status());
return nil;
}
}
return [self initWithCCKeysetHandle:std::move(read_result.value())];
}
return nil;
}
+ (BOOL)deleteFromKeychainWithName:(NSString *)keysetName error:(NSError **)error {
return [self deleteFromKeychainWithName:keysetName accessGroup:nil error:error];
}
+ (BOOL)deleteFromKeychainWithName:(NSString *)keysetName
accessGroup:(NSString *)accessGroup
error:(NSError **)error {
if (keysetName == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
"keysetName must be non-nil."));
}
return NO;
}
NSDictionary *attributes = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : keysetName,
(__bridge id)kSecAttrService : kTinkService,
};
if (accessGroup) {
NSMutableDictionary *mutableAttributes =
[NSMutableDictionary dictionaryWithDictionary:attributes];
[mutableAttributes setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
attributes = [mutableAttributes copy];
}
OSStatus status = SecItemDelete((CFDictionaryRef)attributes);
if (status != errSecSuccess && status != errSecItemNotFound) {
if (error) {
std::ostringstream oss;
oss << "An error occurred 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(absl::StatusCode::kUnknown, errorMessage));
}
return NO;
}
return YES;
}
- (BOOL)writeToKeychainWithName:(NSString *)keysetName
overwrite:(BOOL)overwrite
error:(NSError **)error {
return [self writeToKeychainWithName:keysetName accessGroup:nil overwrite:overwrite error:error];
}
- (BOOL)writeToKeychainWithName:(NSString *)keysetName
accessGroup:(NSString *)accessGroup
overwrite:(BOOL)overwrite
error:(NSError **)error {
if (keysetName == nil) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
"keysetName must be non-nil."));
}
return NO;
}
auto keyset = crypto::tink::CleartextKeysetHandle::GetKeyset(*self.ccKeysetHandle);
std::string serializedKeyset;
if (!keyset.SerializeToString(&serializedKeyset)) {
if (error) {
*error = TINKStatusToError(crypto::tink::util::Status(
absl::StatusCode::kInternal, "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,
};
if (accessGroup) {
NSMutableDictionary *mutableAttributes =
[NSMutableDictionary dictionaryWithDictionary:attributes];
[mutableAttributes setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
attributes = [mutableAttributes copy];
}
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(absl::StatusCode::kUnknown, errorMessage));
}
return NO;
default:
if (error) {
std::ostringstream oss;
oss << "An error occurred while trying to store the keyset in the keychain.";
oss << " Error code: " << status;
std::string errorMessage = oss.str();
*error =
TINKStatusToError(crypto::tink::util::Status(absl::StatusCode::kUnknown, 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.value())];
}
- (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.value())];
}
- (NSData *)serializedKeysetNoSecret:(NSError **)error {
std::stringbuf buffer;
auto writerResult = crypto::tink::BinaryKeysetWriter::New(
absl::make_unique<std::ostream>(&buffer));
if (!writerResult.ok()) {
if (error) {
*error = TINKStatusToError(writerResult.status());
}
return nil;
}
auto writer = std::move(writerResult.value());
auto writeNoSecretStatus = self.ccKeysetHandle->WriteNoSecret(writer.get());
if (!writeNoSecretStatus.ok()) {
if (error) {
*error = TINKStatusToError(writeNoSecretStatus);
}
return nil;
}
return TINKStringToNSData(buffer.str());
}
@end