/*
* Copyright (c) 2017 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
#import <AssertMacros.h>
#import <Foundation/Foundation.h>
#import "CKKSSIV.h"
#include <CommonCrypto/CommonCrypto.h>
#include <CommonCrypto/CommonRandom.h>
#include <corecrypto/ccaes.h>
#include <corecrypto/ccmode_siv.h>
@implementation CKKSBaseAESSIVKey
- (instancetype)init {
if(self = [super init]) {
self->size = CKKSWrappedKeySize;
[self zeroKey];
}
return self;
}
- (instancetype)initWithBytes:(uint8_t *)bytes len:(size_t)len {
if(self = [super init]) {
if(len <= CKKSWrappedKeySize) {
self->size = len;
memcpy(self->key, bytes, self->size);
}
}
return self;
}
- (instancetype)initWithBase64:(NSString*)base64bytes {
if(self = [super init]) {
NSData* data = [[NSData alloc] initWithBase64EncodedString: base64bytes options:0];
if(!data) {
return nil;
}
if(data.length <= CKKSWrappedKeySize) {
self->size = data.length;
memcpy(self->key, data.bytes, self->size);
}
}
return self;
}
- (BOOL)isEqual: (id) object {
if(![object isKindOfClass:[CKKSBaseAESSIVKey class]]) {
return NO;
}
CKKSBaseAESSIVKey* obj = (CKKSBaseAESSIVKey*) object;
if (self->size == obj->size && 0 == memcmp(self->key, obj->key, self->size)) {
return YES;
} else {
return NO;
}
}
- (void)dealloc {
[self zeroKey];
}
- (void)zeroKey {
memset_s(self->key, self->size, 0x00, CKKSWrappedKeySize);
}
- (instancetype)copyWithZone:(NSZone *)zone {
return [[[self class] allocWithZone:zone] initWithBytes:self->key len:self->size];
}
@end
@implementation CKKSWrappedAESSIVKey
- (instancetype)init {
if(self = [super init]) {
self->size = CKKSWrappedKeySize;
}
return self;
}
- (instancetype)initWithBytes:(uint8_t *)bytes len:(size_t)len {
if(len != CKKSWrappedKeySize) {
@throw [NSException
exceptionWithName:@"WrongKeySizeException"
reason:[NSString stringWithFormat: @"length ( userInfo:nil];
}
if(self = [super initWithBytes: bytes len: len]) {
}
return self;
}
- (instancetype)initWithBase64:(NSString*)base64bytes {
if(self = [super initWithBase64: base64bytes]) {
if(self->size != CKKSWrappedKeySize) {
@throw [NSException
exceptionWithName:@"WrongKeySizeException"
reason:[NSString stringWithFormat: @"length ( userInfo:nil];
}
}
return self;
}
- (instancetype)initWithData: (NSData*) data {
if(data.length != CKKSWrappedKeySize) {
@throw [NSException
exceptionWithName:@"WrongKeySizeException"
reason:[NSString stringWithFormat: @"length ( userInfo:nil];
}
if(self = [super initWithBytes: (uint8_t*) data.bytes len: data.length]) {
}
return self;
}
- (NSData*) wrappedData {
return [[NSData alloc] initWithBytes:self->key length:self->size];
}
- (NSString*) base64WrappedKey {
return [[self wrappedData] base64EncodedStringWithOptions:0];
}
@end
@implementation CKKSAESSIVKey
- (instancetype)init {
if(self = [super init]) {
self->size = CKKSKeySize;
}
return self;
}
- (instancetype)initWithBytes:(uint8_t *)bytes len:(size_t)len {
if(len != CKKSKeySize) {
@throw [NSException
exceptionWithName:@"WrongKeySizeException"
reason:[NSString stringWithFormat: @"length ( userInfo:nil];
}
if(self = [super initWithBytes: bytes len: len]) {
}
return self;
}
- (instancetype)initWithBase64:(NSString*)base64bytes {
if(self = [super initWithBase64: base64bytes]) {
if(self->size != CKKSKeySize) {
@throw [NSException
exceptionWithName:@"WrongKeySizeException"
reason:[NSString stringWithFormat: @"length ( userInfo:nil];
}
}
return self;
}
+ (instancetype)randomKey {
CKKSAESSIVKey* key = [[CKKSAESSIVKey alloc] init];
CCRNGStatus status = CCRandomGenerateBytes(key->key, key->size);
if(status != kCCSuccess) {
@throw [NSException
exceptionWithName:@"randomnessException"
reason:[NSString stringWithFormat: @"CCRandomGenerateBytes failed with userInfo:nil];
}
return key;
}
- (CKKSWrappedAESSIVKey*)wrapAESKey: (CKKSAESSIVKey*) keyToWrap error: (NSError * __autoreleasing *) error {
NSError* localerror = nil;
bool success = false;
CKKSWrappedAESSIVKey* wrappedKey = nil;
uint8_t buffer[CKKSWrappedKeySize] = {};
size_t ciphertextLength = ccsiv_ciphertext_size(ccaes_siv_encrypt_mode(), CKKSKeySize);
require_action_quiet(ciphertextLength == CKKSWrappedKeySize, out, localerror = [NSError errorWithDomain:@"securityd"
code:errSecParam
userInfo:@{NSLocalizedDescriptionKey: @"wrapped key size does not match key size"}]);
success = [self doSIV: ccaes_siv_encrypt_mode()
nonce: nil
text: [[NSData alloc] initWithBytesNoCopy: (void*) (keyToWrap->key) length: keyToWrap->size freeWhenDone: NO]
buffer: buffer bufferLength: sizeof(buffer)
authenticatedData: nil
error: error];
require_quiet(success, out);
wrappedKey = [[CKKSWrappedAESSIVKey alloc] initWithBytes:buffer len:sizeof(buffer)];
out:
memset_s(buffer, sizeof(buffer), 0x00, CKKSKeySize);
if(error && localerror != nil) {
*error = localerror;
}
return wrappedKey;
}
- (CKKSAESSIVKey*)unwrapAESKey: (CKKSWrappedAESSIVKey*) keyToUnwrap error: (NSError * __autoreleasing *) error {
NSError* localerror = nil;
bool success = false;
CKKSAESSIVKey* unwrappedKey = nil;
uint8_t buffer[CKKSKeySize] = {};
size_t plaintextLength = ccsiv_plaintext_size(ccaes_siv_decrypt_mode(), CKKSWrappedKeySize);
require_action_quiet(plaintextLength == CKKSKeySize, out, localerror = [NSError errorWithDomain:@"securityd"
code:errSecParam
userInfo:@{NSLocalizedDescriptionKey: @"unwrapped key size does not match key size"}]);
success = [self doSIV: ccaes_siv_decrypt_mode()
nonce: nil
text: [[NSData alloc] initWithBytesNoCopy: (void*) (keyToUnwrap->key) length: keyToUnwrap->size freeWhenDone: NO]
buffer: buffer bufferLength:sizeof(buffer)
authenticatedData: nil
error: error];
require_quiet(success, out);
unwrappedKey = [[CKKSAESSIVKey alloc] initWithBytes: buffer len:sizeof(buffer)];
out:
memset_s(buffer, sizeof(buffer), 0x00, CKKSKeySize);
if(error && localerror != nil) {
*error = localerror;
}
return unwrappedKey;
}
- (NSData*)encryptData: (NSData*) plaintext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
size_t nonceLength = (128/8);
size_t ciphertextLength = 0;
NSMutableData* buffer = nil;
NSMutableData* nonce = nil;
bool success = false;
const struct ccmode_siv* mode = ccaes_siv_encrypt_mode();
// alloc space for nonce and ciphertext.
nonce = [[NSMutableData alloc] initWithLength: nonceLength];
CCRNGStatus status = CCRandomGenerateBytes(nonce.mutableBytes, nonce.length);
if(status != kCCSuccess) {
if(error) {
*error = [NSError errorWithDomain:@"CommonCrypto"
code:status
userInfo:@{NSLocalizedDescriptionKey: @"IV generation failed"}];
}
return nil;
}
ciphertextLength = ccsiv_ciphertext_size(mode, plaintext.length);
buffer = [[NSMutableData alloc] initWithLength: ciphertextLength];
success = [self doSIV: mode
nonce: nonce
text: plaintext
buffer: buffer.mutableBytes
bufferLength: buffer.length
authenticatedData: ad error: error];
if(!success) {
return nil;
}
NSMutableData* ret = [[NSMutableData alloc] init];
[ret appendData: nonce];
[ret appendData: buffer];
return ret;
}
- (NSData*)decryptData: (NSData*) ciphertext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
size_t nonceLength = (128/8);
size_t ciphertextLength = 0;
size_t plaintextLength = 0;
NSMutableData* plaintext = nil;
NSData* nonce = nil;
NSData* text = nil;
bool success = false;
const struct ccmode_siv* mode = ccaes_siv_decrypt_mode();
// compute sizes.
nonceLength = (128/8);
if(ciphertext.length <= nonceLength) {
if(error) {
*error = [NSError errorWithDomain:@"securityd"
code:4
userInfo:@{NSLocalizedDescriptionKey: @"ciphertext too short"}];
}
return nil;
}
ciphertextLength = ciphertext.length - (nonceLength);
// pointer arithmetic. tsk tsk.
nonce = [[NSData alloc] initWithBytesNoCopy: (void*) ciphertext.bytes length: nonceLength freeWhenDone: NO];
text = [[NSData alloc] initWithBytesNoCopy: (void*) (ciphertext.bytes + nonceLength) length: ciphertextLength freeWhenDone: NO];
// alloc space for plaintext
plaintextLength = ccsiv_plaintext_size(mode, ciphertextLength);
plaintext = [[NSMutableData alloc] initWithLength: plaintextLength];
success = [self doSIV: mode
nonce: nonce
text: text
buffer: plaintext.mutableBytes
bufferLength: plaintext.length
authenticatedData: ad error: error];
if(!success) {
return nil;
}
return plaintext;
}
// Does NOT check buffer size. Make sure you get it right for the mode you're requesting!
- (bool)doSIV: (const struct ccmode_siv*) mode nonce: (NSData*) nonce text: (NSData*) text buffer: (uint8_t*) buffer bufferLength: (size_t) bufferLength authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
NSArray<NSString*>* adKeys = nil;
NSError* localerror = nil;
int status = 0;
ccsiv_ctx_decl(mode->size, ctx);
require_action_quiet(mode, out, localerror = [NSError errorWithDomain:@"securityd"
code:1
userInfo:@{NSLocalizedDescriptionKey: @"no mode given"}]);
status = ccsiv_init(mode, ctx, self->size, self->key);
require_action_quiet(status == 0, out, localerror = [NSError errorWithDomain:@"corecrypto"
code:status
userInfo:@{NSLocalizedDescriptionKey: @"could not ccsiv_init"}]);
if(nonce) {
status = ccsiv_set_nonce(mode, ctx, nonce.length, nonce.bytes);
require_action_quiet(status == 0, out, localerror = [NSError errorWithDomain:@"corecrypto"
code:status
userInfo:@{NSLocalizedDescriptionKey: @"could not ccsiv_set_nonce"}]);
}
// Add authenticated data, sorted by Key Order
adKeys = [[ad allKeys] sortedArrayUsingSelector:@selector(compare:)];
for(NSString* adKey in adKeys) {
NSData* adValue = [ad objectForKey: adKey];
status = ccsiv_aad(mode, ctx, adValue.length, adValue.bytes);
require_action_quiet(status == 0, out, localerror = [NSError errorWithDomain:@"corecrypto"
code:status
userInfo:@{NSLocalizedDescriptionKey: @"could not ccsiv_aad"}]);
}
// Actually go.
status = ccsiv_crypt(mode, ctx, text.length, text.bytes, buffer);
require_action_quiet(status == 0, out, localerror = [NSError errorWithDomain:@"corecrypto"
code:status
userInfo:@{NSLocalizedDescriptionKey: @"could not ccsiv_crypt"}]);
out:
ccsiv_ctx_clear(mode->size, ctx);
if(error) {
*error = localerror;
}
return localerror == NULL;
}
@end