MYPrivateKey.m
author Jens Alfke <jens@mooseyard.com>
Fri Aug 07 11:24:53 2009 -0700 (2009-08-07)
changeset 28 54b373aa65ab
parent 23 39fec79de6e8
permissions -rw-r--r--
Fixed iPhone OS build. (issue 3)
snej@3
     1
//
snej@3
     2
//  MYPrivateKey.m
snej@3
     3
//  MYCrypto
snej@3
     4
//
snej@3
     5
//  Created by Jens Alfke on 4/7/09.
snej@3
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@3
     7
//
snej@3
     8
snej@3
     9
#import "MYPrivateKey.h"
snej@3
    10
#import "MYCrypto_Private.h"
snej@3
    11
#import "MYDigest.h"
snej@3
    12
#import <CommonCrypto/CommonDigest.h>
snej@3
    13
snej@5
    14
#if !TARGET_OS_IPHONE
snej@5
    15
#import "MYCertGen.h"
snej@5
    16
#endif
snej@3
    17
snej@3
    18
@implementation MYPrivateKey
snej@3
    19
snej@3
    20
snej@3
    21
- (id) initWithKeyRef: (SecKeyRef)privateKey
snej@3
    22
{
snej@3
    23
    self = [super initWithKeyRef: privateKey];
snej@3
    24
    if (self) {
snej@3
    25
        // No public key given, so look it up:
snej@3
    26
        MYSHA1Digest *digest = self._keyDigest;
snej@3
    27
        if (digest)
snej@3
    28
            _publicKey = [[self.keychain publicKeyWithDigest: digest] retain];
snej@3
    29
        if (!_publicKey) {
snej@3
    30
            // The matching public key won't turn up if it's embedded in a certificate;
snej@3
    31
            // I'd have to search for certs if I wanted to look that up. Skip it for now.
snej@3
    32
            Log(@"MYPrivateKey(%p): Couldn't find matching public key for private key! digest=%@",
snej@3
    33
                self, digest);
snej@3
    34
            [self release];
snej@3
    35
            return nil;
snej@3
    36
        }
snej@3
    37
    }
snej@3
    38
    return self;
snej@3
    39
}
snej@3
    40
snej@3
    41
snej@3
    42
- (id) _initWithKeyRef: (SecKeyRef)privateKey
snej@3
    43
             publicKey: (MYPublicKey*)publicKey 
snej@3
    44
{
snej@3
    45
    Assert(publicKey);
snej@3
    46
    self = [super initWithKeyRef: privateKey];
snej@3
    47
    if (self) {
snej@3
    48
        _publicKey = [publicKey retain];
snej@3
    49
    }
snej@3
    50
    return self;
snej@3
    51
}
snej@3
    52
snej@3
    53
- (id) initWithKeyRef: (SecKeyRef)privateKey
snej@3
    54
         publicKeyRef: (SecKeyRef)publicKeyRef
snej@3
    55
{
snej@3
    56
    MYPublicKey *publicKey = [[MYPublicKey alloc] initWithKeyRef: publicKeyRef];
snej@3
    57
    self = [self _initWithKeyRef: privateKey publicKey: publicKey];
snej@3
    58
    [publicKey release];
snej@3
    59
    return self;
snej@3
    60
}
snej@3
    61
snej@3
    62
- (id) _initWithKeyRef: (SecKeyRef)privateKey 
snej@3
    63
         publicKeyData: (NSData*)pubKeyData
snej@3
    64
           forKeychain: (SecKeychainRef)keychain 
snej@3
    65
{
snej@3
    66
    if (!privateKey) {
snej@3
    67
        [self release];
snej@3
    68
        return nil;
snej@3
    69
    }
snej@3
    70
    MYPublicKey *pubKey = [[MYPublicKey alloc] _initWithKeyData: pubKeyData forKeychain: keychain];
snej@3
    71
    if (!pubKey) {
snej@3
    72
        [self release];
snej@3
    73
        return nil;
snej@3
    74
    }
snej@3
    75
    self = [super initWithKeyRef: privateKey];
snej@3
    76
    if (self) {
snej@3
    77
        _publicKey = pubKey;
snej@3
    78
    } else {
snej@3
    79
        [pubKey removeFromKeychain];
snej@3
    80
        [pubKey release];
snej@3
    81
    }
snej@3
    82
    return self;
snej@3
    83
}
snej@3
    84
snej@3
    85
snej@3
    86
#if !TARGET_OS_IPHONE
snej@3
    87
snej@3
    88
// The public API for this is in MYKeychain.
snej@3
    89
- (id) _initWithKeyData: (NSData*)privKeyData 
snej@3
    90
          publicKeyData: (NSData*)pubKeyData
snej@3
    91
            forKeychain: (SecKeychainRef)keychain 
snej@3
    92
             alertTitle: (NSString*)title
snej@3
    93
            alertPrompt: (NSString*)prompt
snej@3
    94
{
snej@3
    95
    // Try to import the private key first, since the user might cancel the passphrase alert.
snej@3
    96
    SecKeyImportExportParameters params = {
snej@3
    97
        .flags = kSecKeySecurePassphrase,
snej@3
    98
        .alertTitle = (CFStringRef) title,
snej@3
    99
        .alertPrompt = (CFStringRef) prompt
snej@3
   100
    };
snej@3
   101
    SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,&params);
snej@3
   102
    return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
snej@3
   103
}
snej@3
   104
snej@3
   105
// This method is for testing, so unit-tests don't require user intervention.
snej@3
   106
// It's deliberately not made public, to discourage clients from trying to manage the passphrases
snej@3
   107
// themselves (this is less secure than letting the Security agent do it.)
snej@3
   108
- (id) _initWithKeyData: (NSData*)privKeyData 
snej@3
   109
          publicKeyData: (NSData*)pubKeyData
snej@3
   110
            forKeychain: (SecKeychainRef)keychain 
snej@3
   111
             passphrase: (NSString*)passphrase
snej@3
   112
{
snej@3
   113
    SecKeyImportExportParameters params = {
snej@3
   114
        .passphrase = (CFStringRef) passphrase,
snej@3
   115
    };
snej@3
   116
    SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,&params);
snej@3
   117
    return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
snej@3
   118
}
snej@3
   119
snej@4
   120
snej@3
   121
#endif
snej@3
   122
snej@3
   123
snej@3
   124
- (void) dealloc
snej@3
   125
{
snej@3
   126
    [_publicKey release];
snej@3
   127
    [super dealloc];
snej@3
   128
}
snej@3
   129
snej@3
   130
snej@3
   131
+ (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
snej@3
   132
                                 inKeychain: (MYKeychain*)keychain 
snej@3
   133
{
snej@3
   134
    Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
snej@3
   135
    SecKeyRef pubKey=NULL, privKey=NULL;
snej@3
   136
    OSStatus err;
snej@3
   137
    
snej@3
   138
#if MYCRYPTO_USE_IPHONE_API
snej@3
   139
    NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
snej@3
   140
    NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
snej@3
   141
    NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
snej@3
   142
                                    {(id)kSecAttrKeySizeInBits, $object(keySize)},
snej@3
   143
                                    {(id)kSecPublicKeyAttrs, pubKeyAttrs},
snej@3
   144
                                    {(id)kSecPrivateKeyAttrs, privKeyAttrs} );
snej@3
   145
    err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
snej@3
   146
#else
snej@3
   147
    err = SecKeyCreatePair(keychain.keychainRefOrDefault,
snej@3
   148
                           CSSM_ALGID_RSA, 
snej@3
   149
                           keySize,
snej@3
   150
                           0LL,
snej@13
   151
                           CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,        // public key
snej@3
   152
                           CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
snej@8
   153
                           CSSM_KEYUSE_ANY,                                 // private key
snej@8
   154
                           CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
snej@8
   155
                           NULL,                                            // SecAccessRef
snej@3
   156
                           &pubKey, &privKey);
snej@3
   157
#endif
snej@3
   158
    if (!check(err, @"SecKeyCreatePair")) {
snej@3
   159
        return nil;
snej@3
   160
    } else
snej@3
   161
        return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
snej@3
   162
}
snej@3
   163
snej@3
   164
snej@3
   165
#pragma mark -
snej@3
   166
#pragma mark ACCESSORS:
snej@3
   167
snej@3
   168
snej@3
   169
- (NSString*) description {
snej@8
   170
    return $sprintf(@"%@[%@ %@ /%p]", [self class], 
snej@8
   171
                    self.publicKeyDigest.abbreviatedHexString,
snej@8
   172
                    (self.name ?:@""),
snej@8
   173
                    self.keychainItemRef);
snej@3
   174
}
snej@3
   175
snej@3
   176
@synthesize publicKey=_publicKey;
snej@3
   177
jens@23
   178
- (MYSHA1Digest*) _keyDigest {
jens@23
   179
    if (_publicKey)
jens@23
   180
        return _publicKey.publicKeyDigest;
jens@24
   181
    else {
jens@24
   182
        NSData *digestData;
jens@24
   183
#if MYCRYPTO_USE_IPHONE_API
jens@24
   184
        digestData = [self _attribute: kSecAttrApplicationLabel];
jens@24
   185
#else
jens@24
   186
        digestData = [[self class] _getAttribute: kSecKeyLabel 
jens@24
   187
                                          ofItem: (SecKeychainItemRef)self.keyRef]; 
jens@24
   188
#endif
jens@24
   189
        return [MYSHA1Digest digestFromDigestData: digestData];
jens@24
   190
    }
snej@3
   191
}
snej@3
   192
jens@23
   193
- (MYSHA1Digest*) publicKeyDigest {
jens@23
   194
    return self._keyDigest;
jens@23
   195
}
jens@23
   196
jens@23
   197
- (SecExternalItemType) keyClass {
snej@3
   198
#if MYCRYPTO_USE_IPHONE_API
snej@3
   199
    return kSecAttrKeyClassPublic;
snej@3
   200
#else
snej@3
   201
    return kSecItemTypePrivateKey;
snej@3
   202
#endif
snej@3
   203
}
snej@3
   204
jens@23
   205
#if MYCRYPTO_USE_IPHONE_API
jens@23
   206
- (SecExternalItemType) keyType {
jens@23
   207
    return kSecAttrKeyTypeRSA;
jens@23
   208
}
jens@23
   209
#endif
jens@23
   210
snej@3
   211
- (NSData *) keyData {
snej@3
   212
    [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
snej@3
   213
    return nil;
snej@3
   214
}
snej@3
   215
snej@3
   216
- (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
snej@3
   217
    return [super setValue: valueStr ofAttribute: attr]
snej@3
   218
        && [_publicKey setValue: valueStr ofAttribute: attr];
snej@3
   219
}
snej@3
   220
snej@3
   221
snej@3
   222
#pragma mark -
snej@3
   223
#pragma mark OPERATIONS:
snej@3
   224
snej@3
   225
snej@3
   226
- (BOOL) removeFromKeychain {
snej@3
   227
    return [super removeFromKeychain]
snej@3
   228
        && [_publicKey removeFromKeychain];
snej@3
   229
}
snej@3
   230
snej@3
   231
snej@13
   232
- (NSData*) rawDecryptData: (NSData*)data {
snej@3
   233
    return [self _crypt: data operation: NO];
snej@3
   234
}
snej@3
   235
snej@3
   236
snej@3
   237
- (NSData*) signData: (NSData*)data {
snej@3
   238
    Assert(data);
snej@3
   239
#if MYCRYPTO_USE_IPHONE_API
snej@3
   240
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
snej@3
   241
    CC_SHA1(data.bytes,data.length, digest);
snej@3
   242
snej@3
   243
    size_t sigLen = 1024;
snej@3
   244
    uint8_t sigBuf[sigLen];
snej@3
   245
    OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
snej@3
   246
                                 digest,sizeof(digest), //data.bytes, data.length,
snej@3
   247
                                 sigBuf, &sigLen);
snej@3
   248
    if(err) {
snej@3
   249
        Warn(@"SecKeyRawSign failed: %i",err);
snej@3
   250
        return nil;
snej@3
   251
    } else
snej@3
   252
        return [NSData dataWithBytes: sigBuf length: sigLen];
snej@3
   253
#else
snej@3
   254
    NSData *signature = nil;
jens@17
   255
    CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA1WithRSA];
snej@3
   256
    if (!ccHandle) return nil;
snej@3
   257
    CSSM_DATA original = {data.length, (void*)data.bytes};
snej@3
   258
    CSSM_DATA result = {0,NULL};
snej@3
   259
    if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
snej@3
   260
        signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
snej@3
   261
                                   freeWhenDone: YES];
snej@3
   262
    CSSM_DeleteContext(ccHandle);
snej@3
   263
    return signature;
snej@3
   264
#endif
snej@3
   265
}
snej@3
   266
snej@3
   267
snej@3
   268
#if !TARGET_OS_IPHONE
snej@3
   269
snej@3
   270
- (NSData*) exportKeyInFormat: (SecExternalFormat)format 
snej@3
   271
                      withPEM: (BOOL)withPEM
snej@3
   272
                   alertTitle: (NSString*)title
snej@3
   273
                  alertPrompt: (NSString*)prompt
snej@3
   274
{
snej@3
   275
    SecKeyImportExportParameters params = {
snej@3
   276
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   277
        .flags = kSecKeySecurePassphrase,
snej@3
   278
        .alertTitle = (CFStringRef)title,
snej@3
   279
        .alertPrompt = (CFStringRef)prompt
snej@3
   280
    };
snej@3
   281
    CFDataRef data = NULL;
snej@3
   282
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   283
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   284
                                    &params, &data),
snej@3
   285
              @"SecKeychainItemExport"))
snej@3
   286
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   287
    else
snej@3
   288
        return nil;
snej@3
   289
}
snej@3
   290
snej@3
   291
- (NSData*) exportKey {
snej@3
   292
    return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
snej@3
   293
                        alertTitle: @"Export Private Key"
snej@3
   294
                       alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
snej@3
   295
            "You will need to re-enter the passphrase later when importing the key from this file, "
snej@3
   296
            "so keep it in a safe place."];
snej@3
   297
    //FIX: Should make these messages localizable.
snej@3
   298
}
snej@3
   299
snej@3
   300
snej@3
   301
- (NSData*) _exportKeyInFormat: (SecExternalFormat)format
snej@3
   302
                       withPEM: (BOOL)withPEM
snej@3
   303
                    passphrase: (NSString*)passphrase
snej@3
   304
{
snej@3
   305
    SecKeyImportExportParameters params = {
snej@3
   306
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   307
        .passphrase = (CFStringRef)passphrase
snej@3
   308
    };
snej@3
   309
    CFDataRef data = NULL;
snej@3
   310
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   311
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   312
                                    &params, &data),
snej@3
   313
              @"SecKeychainItemExport"))
snej@3
   314
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   315
    else
snej@3
   316
        return nil;
snej@3
   317
}
snej@3
   318
snej@13
   319
snej@13
   320
- (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
snej@13
   321
                       withAlgorithm: (CCAlgorithm)algorithm
snej@13
   322
                          sizeInBits: (unsigned)sizeInBits
snej@13
   323
{
snej@13
   324
    // First create a wrapped-key structure from the data:
snej@13
   325
    CSSM_WRAP_KEY wrappedKey = {
snej@13
   326
        .KeyHeader = {
snej@13
   327
            .BlobType = CSSM_KEYBLOB_WRAPPED,
snej@13
   328
            .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
snej@13
   329
            .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
snej@13
   330
            .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
snej@13
   331
            .LogicalKeySizeInBits = sizeInBits,
snej@13
   332
            .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
snej@13
   333
            .KeyUsage = CSSM_KEYUSE_ANY,
snej@13
   334
            .WrapAlgorithmId = self.cssmAlgorithm,
snej@13
   335
        },
snej@13
   336
        .KeyData = {
snej@13
   337
            .Data = (void*)wrappedData.bytes,
snej@13
   338
            .Length = wrappedData.length
snej@13
   339
        }
snej@13
   340
    };
snej@13
   341
        
snej@13
   342
    const CSSM_ACCESS_CREDENTIALS* credentials;
snej@13
   343
    credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
snej@13
   344
                                               type: kSecCredentialTypeDefault error: nil];
snej@13
   345
    CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
snej@13
   346
    CSSM_CC_HANDLE ctx;
snej@13
   347
    if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
snej@13
   348
                                                    self.cssmAlgorithm,
snej@13
   349
                                                    credentials, 
snej@13
   350
                                                    self.cssmKey,
snej@13
   351
                                                    CSSM_PADDING_PKCS1,
snej@13
   352
                                                    &ctx), 
snej@13
   353
                   @"CSSM_CSP_CreateAsymmetricContext"))
snej@13
   354
        return nil;
snej@13
   355
    
snej@13
   356
    // Now unwrap the key:
snej@13
   357
    MYSymmetricKey *result = nil;
snej@13
   358
    CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
snej@14
   359
    CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
snej@14
   360
    CSSM_DATA descriptiveData = {};
snej@13
   361
    if (checkcssm(CSSM_UnwrapKey(ctx, 
snej@13
   362
                                 self.cssmKey,
snej@13
   363
                                 &wrappedKey,
snej@13
   364
                                 wrappedKey.KeyHeader.KeyUsage,
snej@13
   365
                                 wrappedKey.KeyHeader.KeyAttr,
snej@14
   366
                                 &label,
snej@14
   367
                                 NULL,
snej@13
   368
                                 unwrappedKey,
snej@14
   369
                                 &descriptiveData),
snej@13
   370
                  @"CSSM_UnwrapKey")) {
snej@13
   371
        result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
snej@13
   372
    }
snej@13
   373
    // Finally, delete the context
snej@14
   374
    if (!result)
snej@14
   375
        free(unwrappedKey);
snej@13
   376
    CSSM_DeleteContext(ctx);
snej@13
   377
    return result;
snej@13
   378
}
snej@13
   379
snej@13
   380
snej@13
   381
#endif !TARGET_OS_IPHONE
snej@3
   382
snej@3
   383
@end
snej@14
   384
snej@14
   385
snej@14
   386
snej@14
   387
/*
snej@14
   388
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@14
   389
 
snej@14
   390
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@14
   391
 provided that the following conditions are met:
snej@14
   392
 
snej@14
   393
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@14
   394
 and the following disclaimer.
snej@14
   395
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@14
   396
 and the following disclaimer in the documentation and/or other materials provided with the
snej@14
   397
 distribution.
snej@14
   398
 
snej@14
   399
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@14
   400
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@14
   401
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@14
   402
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@14
   403
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@14
   404
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@14
   405
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@14
   406
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@14
   407
 */