snej@0: // snej@0: // MYSymmetricKey.m snej@0: // MYCrypto snej@0: // snej@0: // Created by Jens Alfke on 4/2/09. snej@0: // Copyright 2009 Jens Alfke. All rights reserved. snej@0: // snej@0: snej@0: #import "MYSymmetricKey.h" snej@0: #import "MYCryptor.h" snej@0: #import "MYCrypto_Private.h" snej@0: snej@0: snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: typedef uint32_t CSSM_ALGORITHMS; snej@0: enum { snej@0: // Taken from cssmtype.h in OS X 10.5 SDK: snej@0: CSSM_ALGID_NONE = 0x00000000L, snej@0: CSSM_ALGID_DES = CSSM_ALGID_NONE + 14, snej@0: CSSM_ALGID_3DES_3KEY_EDE = CSSM_ALGID_NONE + 17, snej@0: CSSM_ALGID_3DES_3KEY = CSSM_ALGID_3DES_3KEY_EDE, snej@0: CSSM_ALGID_RC4 = CSSM_ALGID_NONE + 25, snej@0: CSSM_ALGID_CAST = CSSM_ALGID_NONE + 27, snej@0: CSSM_ALGID_VENDOR_DEFINED = CSSM_ALGID_NONE + 0x80000000L, snej@0: CSSM_ALGID_AES snej@0: }; snej@0: #else snej@0: #import snej@0: #endif snej@0: snej@0: static const CSSM_ALGORITHMS kCSSMAlgorithms[] = { snej@0: CSSM_ALGID_AES, CSSM_ALGID_DES, CSSM_ALGID_3DES_3KEY, CSSM_ALGID_CAST, CSSM_ALGID_RC4 snej@0: }; snej@0: snej@2: static const char *kCCAlgorithmNames[] = {"AES", "DES", "DES^3", "CAST", "RC4"}; snej@2: snej@0: snej@0: #pragma mark - snej@0: @implementation MYSymmetricKey snej@0: snej@0: snej@1: - (id) _initWithKeyData: (NSData*)keyData snej@1: algorithm: (CCAlgorithm)algorithm snej@1: inKeychain: (MYKeychain*)keychain snej@1: { snej@1: Assert(algorithm <= kCCAlgorithmRC4); snej@1: Assert(keyData); snej@1: SecKeyRef keyRef = NULL; snej@2: #if MYCRYPTO_USE_IPHONE_API snej@2: NSNumber *keySizeInBits = [NSNumber numberWithUnsignedInt: keyData.length * 8]; snej@1: NSDictionary *keyAttrs = $dict( {(id)kSecClass, (id)kSecClassKey}, snej@2: //{(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric}, snej@2: {(id)kSecAttrKeyType, [NSNumber numberWithUnsignedInt: kCSSMAlgorithms[algorithm]]}, snej@2: {(id)kSecAttrKeySizeInBits, keySizeInBits}, snej@2: {(id)kSecAttrEffectiveKeySize, keySizeInBits}, snej@1: {(id)kSecAttrIsPermanent, keychain ?$true :$false}, snej@2: {(id)kSecAttrCanEncrypt, $true}, snej@2: {(id)kSecAttrCanDecrypt, $true}, snej@2: {(id)kSecAttrCanWrap, $false}, snej@2: {(id)kSecAttrCanUnwrap, $false}, snej@2: {(id)kSecAttrCanDerive, $false}, snej@2: {(id)kSecAttrCanSign, $false}, snej@2: {(id)kSecAttrCanVerify, $false}, snej@2: {(id)kSecValueData, keyData}, snej@2: //{(id)kSecAttrApplicationTag, [@"foo" dataUsingEncoding: NSUTF8StringEncoding]}, //TEMP snej@2: {(id)kSecReturnPersistentRef, $true}); snej@1: if (!check(SecItemAdd((CFDictionaryRef)keyAttrs, (CFTypeRef*)&keyRef), @"SecItemAdd")) { snej@1: [self release]; snej@1: return nil; snej@1: } snej@2: Assert(keyRef, @"SecItemAdd didn't return anything"); snej@1: #else snej@1: Assert(NO,@"Unimplemented"); //FIX snej@2: /* The technique below doesn't work, because there's no way to tell SecKeychainItemImport snej@2: what algorithm to use when importing a raw key. Still looking for a solution... --jpa 4/2009 snej@2: SecKeyImportExportParameters params = {}; snej@2: keyRef = importKey(keyData, kSecItemTypeSessionKey, keychain.keychainRefOrDefault, ¶ms); snej@2: if (!keyRef) { snej@2: [self release]; snej@2: return nil; snej@2: } snej@2: */ snej@1: #endif snej@1: self = [self initWithKeyRef: keyRef]; snej@1: CFRelease(keyRef); snej@1: return self; snej@1: } snej@1: snej@1: - (id) initWithKeyData: (NSData*)keyData snej@1: algorithm: (CCAlgorithm)algorithm snej@1: { snej@1: return [self _initWithKeyData: keyData algorithm: algorithm inKeychain: nil]; snej@1: } snej@1: snej@0: + (MYSymmetricKey*) _generateSymmetricKeyOfSize: (unsigned)keySizeInBits snej@0: algorithm: (CCAlgorithm)algorithm snej@0: inKeychain: (MYKeychain*)keychain snej@0: { snej@2: #if MYCRYPTO_USE_IPHONE_API snej@2: return [[[self alloc] _initWithKeyData: [MYCryptor randomKeyOfLength: keySizeInBits] snej@1: algorithm: algorithm snej@1: inKeychain: keychain] snej@1: autorelease]; snej@1: #else snej@0: Assert(algorithm <= kCCAlgorithmRC4); snej@0: CSSM_KEYATTR_FLAGS flags = CSSM_KEYATTR_EXTRACTABLE; snej@0: if (keychain) snej@0: flags |= CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_EXTRACTABLE; snej@0: CSSM_KEYUSE usage = CSSM_KEYUSE_ANY; snej@1: SecKeyRef keyRef = NULL; snej@0: if (!check(SecKeyGenerate(keychain.keychainRefOrDefault, // nil kc generates a transient key snej@0: kCSSMAlgorithms[algorithm], snej@0: keySizeInBits, snej@0: 0, usage, flags, NULL, &keyRef), snej@0: @"SecKeyGenerate")) { snej@0: return nil; snej@0: } snej@1: return [[[self alloc] initWithKeyRef: keyRef] autorelease]; snej@0: #endif snej@0: } snej@0: snej@0: + (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits snej@0: algorithm: (CCAlgorithm)algorithm { snej@0: return [self _generateSymmetricKeyOfSize: keySizeInBits snej@0: algorithm: algorithm snej@0: inKeychain: nil]; snej@0: } snej@0: snej@0: snej@2: #if !TARGET_OS_IPHONE snej@2: - (NSData*) exportKeyInFormat: (SecExternalFormat)format snej@2: withPEM: (BOOL)withPEM snej@2: { snej@2: SecKeyImportExportParameters params = { snej@2: .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, snej@2: .flags = kSecKeySecurePassphrase, snej@2: }; snej@2: CFDataRef data = NULL; snej@2: if (check(SecKeychainItemExport(self.keyRef, snej@2: format, (withPEM ?kSecItemPemArmour :0), snej@2: ¶ms, &data), snej@2: @"SecKeychainItemExport")) snej@2: return [(id)CFMakeCollectable(data) autorelease]; snej@2: else snej@2: return nil; snej@2: } snej@2: #endif snej@2: snej@0: snej@0: - (SecExternalItemType) keyType { snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: return kSecAttrKeyClassSymmetric; snej@0: #else snej@0: return kSecItemTypeSessionKey; snej@0: #endif snej@0: } snej@0: snej@0: - (CCAlgorithm) algorithm { snej@0: CSSM_ALGORITHMS cssmAlg; snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: id keyType = [self _attribute: kSecAttrKeyType]; snej@0: Assert(keyType!=nil, @"Key has no kSecAttrKeyType"); snej@0: cssmAlg = [keyType unsignedIntValue]; snej@0: #else snej@0: cssmAlg = self.cssmKey->KeyHeader.AlgorithmId; snej@0: #endif snej@0: switch(cssmAlg) { snej@0: case CSSM_ALGID_AES: snej@0: return kCCAlgorithmAES128; snej@0: case CSSM_ALGID_DES: snej@0: return kCCAlgorithmDES; snej@0: case CSSM_ALGID_3DES_3KEY: snej@0: return kCCAlgorithm3DES; snej@0: case CSSM_ALGID_CAST: snej@0: return kCCAlgorithmCAST; snej@0: case CSSM_ALGID_RC4: snej@0: return kCCAlgorithmRC4; snej@0: default: snej@0: Warn(@"CSSM_ALGORITHMS #%u doesn't map to any CCAlgorithm", cssmAlg); snej@0: return (CCAlgorithm)-1; snej@0: } snej@0: } snej@0: snej@2: - (const char*) algorithmName { snej@2: CCAlgorithm a = self.algorithm; snej@2: if (a >= 0 && a <= kCCAlgorithmRC4) snej@2: return kCCAlgorithmNames[a]; snej@2: else snej@2: return "???"; snej@2: } snej@2: snej@2: - (unsigned) keySizeInBits { snej@2: #if MYCRYPTO_USE_IPHONE_API snej@2: id keySize = [self _attribute: kSecAttrKeySizeInBits]; snej@2: Assert(keySize!=nil, @"Key has no kSecAttrKeySizeInBits"); snej@2: return [keySize unsignedIntValue]; snej@2: #else snej@2: const CSSM_KEY *key = self.cssmKey; snej@2: Assert(key); snej@2: return key->KeyHeader.LogicalKeySizeInBits; snej@2: #endif snej@2: } snej@2: snej@2: snej@2: - (NSString*) description { snej@2: return $sprintf(@"%@[%u-bit %s]", [self class], self.keySizeInBits, self.algorithmName); snej@2: } snej@2: snej@0: snej@0: - (NSData*) _cryptData: (NSData*)data operation: (CCOperation)op options: (CCOptions)options snej@0: { snej@0: NSData *keyData = self.keyData; snej@0: Assert(keyData, @"Couldn't get key data"); snej@0: NSMutableData *output = [NSMutableData dataWithLength: data.length + 256]; snej@0: size_t bytesWritten = 0; snej@0: CCCryptorStatus status = CCCrypt(op, self.algorithm, options, snej@0: keyData.bytes, keyData.length, NULL, snej@0: data.bytes, data.length, output.mutableBytes, output.length, snej@0: &bytesWritten); snej@0: if (status) { snej@0: Warn(@"MYSymmetricKey: CCCrypt returned error %i",status); snej@0: return nil; snej@0: } snej@0: output.length = bytesWritten; snej@0: return output; snej@0: } snej@0: snej@0: - (NSData*) encryptData: (NSData*)data { snej@0: return [self _cryptData: data operation: kCCEncrypt options: kCCOptionPKCS7Padding]; snej@0: } snej@0: snej@0: snej@0: - (NSData*) decryptData: (NSData*)data { snej@0: return [self _cryptData: data operation: kCCDecrypt options: kCCOptionPKCS7Padding]; snej@0: } snej@0: snej@0: snej@0: @end snej@0: snej@0: snej@0: /* (Turned out I could just use SecKeyExport for this.) snej@0: snej@0: static NSData* wrap(SecKeyRef key, CSSM_ALGORITHMS algorithm) { snej@0: CAssert(key); snej@0: const CSSM_KEY* cssmKey; snej@0: const CSSM_ACCESS_CREDENTIALS *credentials; snej@0: CSSM_CSP_HANDLE cspHandle; snej@0: CSSM_CC_HANDLE ccHandle; snej@0: if (!check(SecKeyGetCSSMKey(key, &cssmKey), @"GetCSSMKey") snej@0: || !check(SecKeyGetCredentials(key, snej@0: CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED, snej@0: kSecCredentialTypeDefault, snej@0: &credentials), @"GetCredentials") snej@0: || !check(SecKeyGetCSPHandle(key, &cspHandle), @"GetCSPHandle") snej@0: snej@0: || !checkcssm(CSSM_CSP_CreateSymmetricContext(cspHandle, algorithm, CSSM_ALGMODE_WRAP, snej@0: NULL, NULL, NULL, snej@0: CSSM_PADDING_NONE, NULL, &ccHandle), snej@0: @"CSSM_CSP_CreateSymmetricContext")) snej@0: return nil; snej@0: snej@0: CSSM_WRAP_KEY wrapped; snej@0: NSData *result = nil; snej@0: if(checkcssm(CSSM_WrapKey(ccHandle, credentials, cssmKey, NULL, &wrapped), snej@0: @"CSSM_WrapKey")) { snej@0: result = [NSData dataWithBytes: wrapped.KeyData.Data snej@0: length: wrapped.KeyData.Length]; snej@0: } snej@0: CSSM_DeleteContext(ccHandle); snej@0: return result; snej@0: } snej@0: */