//
// SecRecoveryKey.c
//
#import "SecRecoveryKey.h"
#import <dispatch/dispatch.h>
#import <corecrypto/cchkdf.h>
#import <corecrypto/ccsha2.h>
#import <corecrypto/ccec.h>
#import <utilities/SecCFWrappers.h>
#import <CommonCrypto/CommonRandomSPI.h>
#import <AssertMacros.h>
#import <Security/SecureObjectSync/SOSCloudCircle.h>
#import <Security/SecureObjectSync/SOSInternal.h>
#if !TARGET_OS_BRIDGE
#include <dlfcn.h>
#include <AppleIDAuthSupport/AppleIDAuthSupport.h>
#define PATH_FOR_APPLEIDAUTHSUPPORTFRAMEWORK "/System/Library/PrivateFrameworks/AppleIDAuthSupport.framework/AppleIDAuthSupport"
#endif
#import "SecCFAllocator.h"
#import "SecPasswordGenerate.h"
#import "SecBase64.h"
typedef struct _CFSecRecoveryKey *CFSecRecoveryKeyRef;
static uint8_t backupPublicKey[] = { 'B', 'a', 'c', 'k', 'u', ' ', 'P', 'u', 'b', 'l', 'i', 'c', 'k', 'e', 'y' };
static uint8_t passwordInfoKey[] = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', ' ', 's', 'e', 'c', 'r', 'e', 't' };
#if !(defined(__i386__) || TARGET_IPHONE_SIMULATOR || TARGET_OS_BRIDGE)
static uint8_t masterkeyIDSalt[] = { 'M', 'a', 's', 't', 'e', 'r', ' ', 'K', 'e', 'y', ' ', 'I', 'd', 'e', 't' };
#endif
#define RK_BACKUP_HKDF_SIZE 128
#define RK_PASSWORD_HKDF_SIZE 32
CFGiblisFor(CFSecRecoveryKey);
struct _CFSecRecoveryKey {
CFRuntimeBase _base;
CFDataRef basecode;
};
static void
CFSecRecoveryKeyDestroy(CFTypeRef cf)
{
CFSecRecoveryKeyRef rk = (CFSecRecoveryKeyRef)cf;
CFReleaseNull(rk->basecode);
}
static CFStringRef
CFSecRecoveryKeyCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions)
{
return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SecRecoveryKey: }
static bool
ValidateRecoveryKey(CFStringRef masterkey, NSError **error)
{
CFErrorRef cferror = NULL;
bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, masterkey, &cferror);
if (!res) {
if (error) {
*error = CFBridgingRelease(cferror);
} else {
CFReleaseNull(cferror);
}
}
return res;
}
NSString *
SecRKCreateRecoveryKeyString(NSError **error)
{
CFErrorRef cferror = NULL;
CFStringRef recoveryKey = SecPasswordGenerate(kSecPasswordTypeiCloudRecoveryKey, &cferror, NULL);
if (recoveryKey == NULL) {
if (error) {
*error = CFBridgingRelease(cferror);
} else {
CFReleaseNull(cferror);
}
return NULL;
}
if (!ValidateRecoveryKey(recoveryKey, error)) {
CFRelease(recoveryKey);
return NULL;
}
return (__bridge NSString *)recoveryKey;
}
SecRecoveryKey *
SecRKCreateRecoveryKey(NSString *masterKey)
{
return SecRKCreateRecoveryKeyWithError(masterKey, NULL);
}
SecRecoveryKey *
SecRKCreateRecoveryKeyWithError(NSString *masterKey, NSError **error)
{
if (!ValidateRecoveryKey((__bridge CFStringRef)masterKey, error)) {
return NULL;
}
CFSecRecoveryKeyRef rk = CFTypeAllocate(CFSecRecoveryKey, struct _CFSecRecoveryKey, NULL);
if (rk == NULL)
return NULL;
rk->basecode = CFStringCreateExternalRepresentation(SecCFAllocatorZeroize(),
(__bridge CFStringRef)masterKey,
kCFStringEncodingUTF8, 0);
if (rk->basecode == NULL) {
CFRelease(rk);
return NULL;
}
return (SecRecoveryKey *) CFBridgingRelease(rk);
}
static CFDataRef
SecRKCreateDerivedSecret(CFSecRecoveryKeyRef rk, CFIndex outputLength,
const uint8_t *variant, size_t variantLength)
{
CFMutableDataRef derived;
int status;
derived = CFDataCreateMutableWithScratch(SecCFAllocatorZeroize(), outputLength);
if (derived == NULL)
return NULL;
status = cchkdf(ccsha256_di(),
CFDataGetLength(rk->basecode), CFDataGetBytePtr(rk->basecode),
4, "salt",
variantLength, variant,
CFDataGetLength(derived), CFDataGetMutableBytePtr(derived));
if (status) {
CFReleaseNull(derived);
}
return derived;
}
NSString *
SecRKCopyAccountRecoveryPassword(SecRecoveryKey *rk)
{
CFStringRef base64Data = NULL;
CFDataRef derived = NULL;
void *b64string = NULL;
size_t base64Len = 0;
derived = SecRKCreateDerivedSecret((__bridge CFSecRecoveryKeyRef)rk,
RK_PASSWORD_HKDF_SIZE,
passwordInfoKey, sizeof(passwordInfoKey));
require(derived, fail);
base64Len = SecBase64Encode(CFDataGetBytePtr(derived), CFDataGetLength(derived), NULL, 0);
assert(base64Len < 1024);
b64string = malloc(base64Len);
require(b64string, fail);
SecBase64Encode(CFDataGetBytePtr(derived), CFDataGetLength(derived), b64string, base64Len);
base64Data = CFStringCreateWithBytes(SecCFAllocatorZeroize(),
(const UInt8 *)b64string, base64Len,
kCFStringEncodingUTF8, false);
require(base64Data, fail);
fail:
if (b64string) {
cc_clear(base64Len, b64string);
free(b64string);
}
CFReleaseNull(derived);
return (__bridge NSString *)base64Data;
}
// We should gen salt/iteration - use S2K for kdf for the time being
// Pass back a dictionary of the parms
//
// Need companion call to respond with MRK on the "iforgot" sequence.
NSString *const kSecRVSalt = @"s";
NSString *const kSecRVIterations = @"i";
NSString *const kSecRVProtocol = @"p";
NSString *const kSecRVVerifier = @"v";
NSString *const kSecRVMasterID = @"mkid";
#if !TARGET_OS_BRIDGE
CFStringRef localProtocolSRPGROUP;
CFDataRef (*localAppleIDauthSupportCreateVerifierPtr) (CFStringRef proto,
CFStringRef username,
CFDataRef salt,
CFNumberRef iter,
CFStringRef password,
CFErrorRef *error);
#if !(defined(__i386__) || TARGET_IPHONE_SIMULATOR)
static CFStringRef getdlsymforString(void *framework, const char *symbol) {
CFStringRef retval = NULL;
void *tmpptr = dlsym(framework, symbol);
if(tmpptr) {
retval = *(CFStringRef*) tmpptr;
}
return retval;
}
static bool connectAppleIDFrameworkSymbols(void) {
static dispatch_once_t onceToken;
static void* framework = NULL;
dispatch_once(&onceToken, ^{
localAppleIDauthSupportCreateVerifierPtr = NULL;
localProtocolSRPGROUP = NULL;
framework = dlopen(PATH_FOR_APPLEIDAUTHSUPPORTFRAMEWORK, RTLD_NOW);
if(framework) {
localProtocolSRPGROUP = getdlsymforString(framework,
"kAppleIDAuthSupportProtocolSRPGROUP2048SHA256PBKDF");
localAppleIDauthSupportCreateVerifierPtr =
dlsym(framework, "AppleIDAuthSupportCreateVerifier");
}
});
return (framework != NULL && localProtocolSRPGROUP != NULL &&
localAppleIDauthSupportCreateVerifierPtr != NULL);
}
#endif
#endif
NSDictionary *
SecRKCopyAccountRecoveryVerifier(NSString *recoveryKey,
NSError **error) {
#if defined(__i386__) || TARGET_IPHONE_SIMULATOR || TARGET_OS_BRIDGE
abort();
return NULL;
#else
CFErrorRef localError = NULL;
CFStringRef username = CFSTR("foo");
NSDictionary *retval = nil;
if(!connectAppleIDFrameworkSymbols()) {
SOSCreateError(kSOSErrorUnsupported, CFSTR("Recovery Key Creation Not Supported on this platform"), NULL, &localError);
if(error) *error = (__bridge_transfer NSError *) localError;
return NULL;
}
NSData *salt = (__bridge_transfer NSData*) CFDataCreateWithRandomBytes(32);
NSNumber *iterations = @40000;
NSString *protocol = (__bridge NSString*) localProtocolSRPGROUP;
NSData *verifier = (__bridge_transfer NSData*) localAppleIDauthSupportCreateVerifierPtr(
localProtocolSRPGROUP,
username,
(__bridge CFDataRef) salt,
(__bridge CFNumberRef) iterations,
(__bridge CFStringRef) (recoveryKey),
&localError);
SecRecoveryKey *srk = SecRKCreateRecoveryKey(recoveryKey);
NSData *masterKeyID = (__bridge_transfer NSData*) SecRKCreateDerivedSecret(
(__bridge CFSecRecoveryKeyRef) srk,
RK_PASSWORD_HKDF_SIZE,
masterkeyIDSalt,
sizeof(masterkeyIDSalt));
if(verifier && masterKeyID) {
retval = @{ kSecRVSalt: salt,
kSecRVIterations: iterations,
kSecRVProtocol: protocol,
kSecRVVerifier: verifier,
kSecRVMasterID: masterKeyID };
} else {
if(error && localError) *error = (__bridge NSError *) localError;
}
return retval;
#endif
}
// This recreates the key pair using the recovery key string.
static NSData *
RKBackupCreateECKey(SecRecoveryKey *rk, bool returnFullkey)
{
CFMutableDataRef keyData = NULL;
CFDataRef derivedSecret = NULL;
ccec_const_cp_t cp = ccec_cp_256();
CFDataRef result = NULL;
int status;
ccec_full_ctx_decl_cp(cp, fullKey);
derivedSecret = SecRKCreateDerivedSecret((__bridge CFSecRecoveryKeyRef)rk, RK_BACKUP_HKDF_SIZE,
backupPublicKey, sizeof(backupPublicKey));
require(derivedSecret, fail);
status = ccec_generate_key_deterministic(cp,
CFDataGetLength(derivedSecret), CFDataGetBytePtr(derivedSecret),
ccDRBGGetRngState(),
CCEC_GENKEY_DETERMINISTIC_COMPACT,
fullKey);
require_noerr(status, fail);
size_t space = ccec_compact_export_size(returnFullkey, ccec_ctx_pub(fullKey));
keyData = CFDataCreateMutableWithScratch(SecCFAllocatorZeroize(), space);
require_quiet(keyData, fail);
ccec_compact_export(returnFullkey, CFDataGetMutableBytePtr(keyData), fullKey);
CFTransferRetained(result, keyData);
fail:
CFReleaseNull(derivedSecret);
CFReleaseNull(keyData);
return (__bridge NSData *)result;
}
NSData *
SecRKCopyBackupFullKey(SecRecoveryKey *rk)
{
return RKBackupCreateECKey(rk, true);
}
NSData *
SecRKCopyBackupPublicKey(SecRecoveryKey *rk)
{
return RKBackupCreateECKey(rk, false);
}
bool
SecRKRegisterBackupPublicKey(SecRecoveryKey *rk, CFErrorRef *error)
{
CFDataRef backupKey = (__bridge CFDataRef)SecRKCopyBackupPublicKey(rk);
bool res = false;
require_action_quiet(backupKey, fail, SOSCreateError(kSOSErrorBadKey, CFSTR("Failed to create key from rk"), NULL, error));
res = SOSCCRegisterRecoveryPublicKey(backupKey, error);
fail:
CFReleaseNull(backupKey);
return res;
}