MYSymmetricKey.m
author snej@snej.local
Sun Apr 12 22:16:14 2009 -0700 (2009-04-12)
changeset 9 aa5eb3fd6ebf
parent 4 f4709533c816
child 12 e4c971be4079
permissions -rw-r--r--
Doc touch-up
     1 //
     2 //  MYSymmetricKey.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 4/2/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYSymmetricKey.h"
    10 #import "MYCryptor.h"
    11 #import "MYCrypto_Private.h"
    12 
    13 
    14 #if MYCRYPTO_USE_IPHONE_API
    15 typedef uint32_t CSSM_ALGORITHMS;
    16 enum {
    17 // Taken from cssmtype.h in OS X 10.5 SDK:
    18 	CSSM_ALGID_NONE =					0x00000000L,
    19 	CSSM_ALGID_DES =					CSSM_ALGID_NONE + 14,
    20 	CSSM_ALGID_3DES_3KEY_EDE =			CSSM_ALGID_NONE + 17,
    21 	CSSM_ALGID_3DES_3KEY =           	CSSM_ALGID_3DES_3KEY_EDE,
    22 	CSSM_ALGID_RC4 =					CSSM_ALGID_NONE + 25,
    23 	CSSM_ALGID_CAST =					CSSM_ALGID_NONE + 27,
    24 	CSSM_ALGID_VENDOR_DEFINED =			CSSM_ALGID_NONE + 0x80000000L,
    25 	CSSM_ALGID_AES
    26 };
    27 #else
    28 #import <Security/cssmtype.h>
    29 #endif
    30 
    31 static const CSSM_ALGORITHMS kCSSMAlgorithms[] = {
    32     CSSM_ALGID_AES, CSSM_ALGID_DES, CSSM_ALGID_3DES_3KEY, CSSM_ALGID_CAST, CSSM_ALGID_RC4
    33 };
    34 
    35 static const char *kCCAlgorithmNames[] = {"AES", "DES", "DES^3", "CAST", "RC4"};
    36 
    37 
    38 #pragma mark -
    39 @implementation MYSymmetricKey
    40 
    41 
    42 - (id) _initWithKeyData: (NSData*)keyData
    43               algorithm: (CCAlgorithm)algorithm
    44              inKeychain: (MYKeychain*)keychain
    45 {
    46     Assert(algorithm <= kCCAlgorithmRC4);
    47     Assert(keyData);
    48     SecKeyRef keyRef = NULL;
    49 #if MYCRYPTO_USE_IPHONE_API
    50     NSNumber *keySizeInBits = [NSNumber numberWithUnsignedInt: keyData.length * 8];
    51     NSDictionary *keyAttrs = $dict( {(id)kSecClass, (id)kSecClassKey},
    52                                     //{(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric},
    53                                     {(id)kSecAttrKeyType, [NSNumber numberWithUnsignedInt: kCSSMAlgorithms[algorithm]]},
    54                                     {(id)kSecAttrKeySizeInBits, keySizeInBits},
    55                                     {(id)kSecAttrEffectiveKeySize, keySizeInBits},
    56                                     {(id)kSecAttrIsPermanent, keychain ?$true :$false},
    57                                     {(id)kSecAttrCanEncrypt, $true},
    58                                     {(id)kSecAttrCanDecrypt, $true},
    59                                     {(id)kSecAttrCanWrap, $false},
    60                                     {(id)kSecAttrCanUnwrap, $false},
    61                                     {(id)kSecAttrCanDerive, $false},
    62                                     {(id)kSecAttrCanSign, $false},
    63                                     {(id)kSecAttrCanVerify, $false},
    64                                     {(id)kSecValueData, keyData},
    65                                     {(id)kSecReturnPersistentRef, $true});
    66     if (!check(SecItemAdd((CFDictionaryRef)keyAttrs, (CFTypeRef*)&keyRef), @"SecItemAdd")) {
    67         [self release];
    68         return nil;
    69     }
    70     Assert(keyRef, @"SecItemAdd didn't return anything");
    71 #else
    72     Assert(NO,@"Unimplemented"); //FIX
    73     /* The technique below doesn't work, because there's no way to tell SecKeychainItemImport
    74        what algorithm to use when importing a raw key. Still looking for a solution... --jpa 4/2009
    75     SecKeyImportExportParameters params = {};
    76     keyRef = importKey(keyData, kSecItemTypeSessionKey, keychain.keychainRefOrDefault, &params);
    77     if (!keyRef) {
    78         [self release];
    79         return nil;
    80     }
    81      */
    82 #endif
    83     self = [self initWithKeyRef: keyRef];
    84     CFRelease(keyRef);
    85     return self;
    86 }
    87 
    88 - (id) initWithKeyData: (NSData*)keyData
    89              algorithm: (CCAlgorithm)algorithm
    90 {
    91     return [self _initWithKeyData: keyData algorithm: algorithm inKeychain: nil];
    92 }
    93 
    94 + (MYSymmetricKey*) _generateSymmetricKeyOfSize: (unsigned)keySizeInBits
    95                                       algorithm: (CCAlgorithm)algorithm
    96                                      inKeychain: (MYKeychain*)keychain
    97 {
    98 #if MYCRYPTO_USE_IPHONE_API
    99     return [[[self alloc] _initWithKeyData: [MYCryptor randomKeyOfLength: keySizeInBits]
   100                                  algorithm: algorithm
   101                                 inKeychain: keychain]
   102                     autorelease];
   103 #else
   104     Assert(algorithm <= kCCAlgorithmRC4);
   105     CSSM_KEYATTR_FLAGS flags = CSSM_KEYATTR_EXTRACTABLE;
   106     if (keychain)
   107         flags |= CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_EXTRACTABLE;
   108     CSSM_KEYUSE usage = CSSM_KEYUSE_ANY;
   109     SecKeyRef keyRef = NULL;
   110     if (!check(SecKeyGenerate(keychain.keychainRefOrDefault,    // nil kc generates a transient key
   111                               kCSSMAlgorithms[algorithm],
   112                               keySizeInBits, 
   113                               0, usage, flags, NULL, &keyRef),
   114                @"SecKeyGenerate")) {
   115         return nil;
   116     }
   117     return [[[self alloc] initWithKeyRef: keyRef] autorelease];
   118 #endif
   119 }
   120 
   121 + (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
   122                                      algorithm: (CCAlgorithm)algorithm {
   123     return [self _generateSymmetricKeyOfSize: keySizeInBits
   124                                    algorithm: algorithm
   125                                   inKeychain: nil];
   126 }
   127 
   128 
   129 #if !TARGET_OS_IPHONE
   130 - (NSData*) exportKeyInFormat: (SecExternalFormat)format
   131                       withPEM: (BOOL)withPEM
   132 {
   133     SecKeyImportExportParameters params = {
   134         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   135         .flags = kSecKeySecurePassphrase,
   136     };
   137     CFDataRef data = NULL;
   138     if (check(SecKeychainItemExport(self.keyRef,
   139                                     format, (withPEM ?kSecItemPemArmour :0), 
   140                                     &params, &data),
   141               @"SecKeychainItemExport"))
   142         return [(id)CFMakeCollectable(data) autorelease];
   143     else
   144         return nil;
   145 }
   146 #endif
   147 
   148 
   149 - (SecExternalItemType) keyType {
   150 #if MYCRYPTO_USE_IPHONE_API
   151     return kSecAttrKeyClassSymmetric;
   152 #else
   153     return kSecItemTypeSessionKey;
   154 #endif
   155 }
   156 
   157 - (CCAlgorithm) algorithm {
   158     CSSM_ALGORITHMS cssmAlg;
   159 #if MYCRYPTO_USE_IPHONE_API
   160     id keyType = [self _attribute: kSecAttrKeyType];
   161     Assert(keyType!=nil, @"Key has no kSecAttrKeyType");
   162     cssmAlg = [keyType unsignedIntValue];
   163 #else
   164     cssmAlg = self.cssmKey->KeyHeader.AlgorithmId;
   165 #endif
   166     switch(cssmAlg) {
   167         case CSSM_ALGID_AES:
   168             return kCCAlgorithmAES128;
   169         case CSSM_ALGID_DES:
   170             return kCCAlgorithmDES;	
   171         case CSSM_ALGID_3DES_3KEY:
   172             return kCCAlgorithm3DES;
   173         case CSSM_ALGID_CAST:
   174             return kCCAlgorithmCAST;
   175         case CSSM_ALGID_RC4:
   176             return kCCAlgorithmRC4;	
   177         default:
   178             Warn(@"CSSM_ALGORITHMS #%u doesn't map to any CCAlgorithm", cssmAlg);
   179             return (CCAlgorithm)-1;
   180     }
   181 }
   182 
   183 - (const char*) algorithmName {
   184     CCAlgorithm a = self.algorithm;
   185     if (a >= 0 && a <= kCCAlgorithmRC4)
   186         return kCCAlgorithmNames[a];
   187     else
   188         return "???";
   189 }
   190 
   191 - (unsigned) keySizeInBits {
   192 #if MYCRYPTO_USE_IPHONE_API
   193     id keySize = [self _attribute: kSecAttrKeySizeInBits];
   194     Assert(keySize!=nil, @"Key has no kSecAttrKeySizeInBits");
   195     return [keySize unsignedIntValue];
   196 #else
   197     const CSSM_KEY *key = self.cssmKey;
   198     Assert(key);
   199     return key->KeyHeader.LogicalKeySizeInBits;
   200 #endif
   201 }
   202 
   203 
   204 - (NSString*) description {
   205     return $sprintf(@"%@[%u-bit %s]", [self class], self.keySizeInBits, self.algorithmName);
   206 }
   207 
   208 
   209 - (NSData*) _cryptData: (NSData*)data operation: (CCOperation)op options: (CCOptions)options
   210 {
   211     NSData *keyData = self.keyData;
   212     Assert(keyData, @"Couldn't get key data");
   213     NSMutableData *output = [NSMutableData dataWithLength: data.length + 256];
   214     size_t bytesWritten = 0;
   215     CCCryptorStatus status = CCCrypt(op, self.algorithm, options, 
   216                                      keyData.bytes, keyData.length, NULL,
   217                                      data.bytes, data.length, output.mutableBytes, output.length,
   218                                      &bytesWritten);
   219     if (status) {
   220         Warn(@"MYSymmetricKey: CCCrypt returned error %i",status);
   221         return nil;
   222     }
   223     output.length = bytesWritten;
   224     return output;
   225 }
   226 
   227 - (NSData*) encryptData: (NSData*)data {
   228     return [self _cryptData: data operation: kCCEncrypt options: kCCOptionPKCS7Padding];
   229 }
   230 
   231 
   232 - (NSData*) decryptData: (NSData*)data {
   233     return [self _cryptData: data operation: kCCDecrypt options: kCCOptionPKCS7Padding];
   234 }
   235 
   236 
   237 @end
   238 
   239 
   240 /* (Turned out I could just use SecKeyExport for this.)
   241  
   242 static NSData* wrap(SecKeyRef key, CSSM_ALGORITHMS algorithm) {
   243     CAssert(key);
   244     const CSSM_KEY* cssmKey;
   245     const CSSM_ACCESS_CREDENTIALS *credentials;
   246     CSSM_CSP_HANDLE cspHandle;
   247     CSSM_CC_HANDLE ccHandle;
   248     if (!check(SecKeyGetCSSMKey(key, &cssmKey), @"GetCSSMKey")
   249         || !check(SecKeyGetCredentials(key, 
   250                                        CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED, 
   251                                        kSecCredentialTypeDefault,
   252                                        &credentials), @"GetCredentials")
   253         || !check(SecKeyGetCSPHandle(key, &cspHandle), @"GetCSPHandle")
   254         
   255         || !checkcssm(CSSM_CSP_CreateSymmetricContext(cspHandle, algorithm, CSSM_ALGMODE_WRAP,
   256                                                       NULL, NULL, NULL,
   257                                                       CSSM_PADDING_NONE, NULL, &ccHandle),
   258                       @"CSSM_CSP_CreateSymmetricContext"))
   259         return nil;
   260     
   261     CSSM_WRAP_KEY wrapped;
   262     NSData *result = nil;
   263     if(checkcssm(CSSM_WrapKey(ccHandle, credentials, cssmKey, NULL, &wrapped),
   264                  @"CSSM_WrapKey")) {
   265         result = [NSData dataWithBytes: wrapped.KeyData.Data 
   266                                 length: wrapped.KeyData.Length];
   267     }
   268     CSSM_DeleteContext(ccHandle);
   269     return result;
   270 }
   271 */