MYPrivateKey.m
author Jens Alfke <jens@mooseyard.com>
Sat Jun 06 15:01:28 2009 -0700 (2009-06-06)
changeset 21 2c300b15b381
parent 17 90a70925562b
child 23 39fec79de6e8
permissions -rw-r--r--
* Created class MYCertificateRequest, factored out of MYCertificateInfo.
* Added method to create a MYIdentity directly from a MYCertificateRequest.
* Added raw modulus+exponent accessor and initializer for MYPublicKey.
* Removed obsolete MYCertGen code, and the MYPrivateKey identity-creation method that used it.
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
snej@3
   178
- (MYSHA1Digest*) publicKeyDigest {
snej@3
   179
    return _publicKey.publicKeyDigest;
snej@3
   180
}
snej@3
   181
snej@3
   182
- (SecExternalItemType) keyType {
snej@3
   183
#if MYCRYPTO_USE_IPHONE_API
snej@3
   184
    return kSecAttrKeyClassPublic;
snej@3
   185
#else
snej@3
   186
    return kSecItemTypePrivateKey;
snej@3
   187
#endif
snej@3
   188
}
snej@3
   189
snej@3
   190
- (NSData *) keyData {
snej@3
   191
    [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
snej@3
   192
    return nil;
snej@3
   193
}
snej@3
   194
snej@3
   195
- (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
snej@3
   196
    return [super setValue: valueStr ofAttribute: attr]
snej@3
   197
        && [_publicKey setValue: valueStr ofAttribute: attr];
snej@3
   198
}
snej@3
   199
snej@3
   200
snej@3
   201
#pragma mark -
snej@3
   202
#pragma mark OPERATIONS:
snej@3
   203
snej@3
   204
snej@3
   205
- (BOOL) removeFromKeychain {
snej@3
   206
    return [super removeFromKeychain]
snej@3
   207
        && [_publicKey removeFromKeychain];
snej@3
   208
}
snej@3
   209
snej@3
   210
snej@13
   211
- (NSData*) rawDecryptData: (NSData*)data {
snej@3
   212
    return [self _crypt: data operation: NO];
snej@3
   213
}
snej@3
   214
snej@3
   215
snej@3
   216
- (NSData*) signData: (NSData*)data {
snej@3
   217
    Assert(data);
snej@3
   218
#if MYCRYPTO_USE_IPHONE_API
snej@3
   219
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
snej@3
   220
    CC_SHA1(data.bytes,data.length, digest);
snej@3
   221
snej@3
   222
    size_t sigLen = 1024;
snej@3
   223
    uint8_t sigBuf[sigLen];
snej@3
   224
    OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
snej@3
   225
                                 digest,sizeof(digest), //data.bytes, data.length,
snej@3
   226
                                 sigBuf, &sigLen);
snej@3
   227
    if(err) {
snej@3
   228
        Warn(@"SecKeyRawSign failed: %i",err);
snej@3
   229
        return nil;
snej@3
   230
    } else
snej@3
   231
        return [NSData dataWithBytes: sigBuf length: sigLen];
snej@3
   232
#else
snej@3
   233
    NSData *signature = nil;
jens@17
   234
    CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA1WithRSA];
snej@3
   235
    if (!ccHandle) return nil;
snej@3
   236
    CSSM_DATA original = {data.length, (void*)data.bytes};
snej@3
   237
    CSSM_DATA result = {0,NULL};
snej@3
   238
    if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
snej@3
   239
        signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
snej@3
   240
                                   freeWhenDone: YES];
snej@3
   241
    CSSM_DeleteContext(ccHandle);
snej@3
   242
    return signature;
snej@3
   243
#endif
snej@3
   244
}
snej@3
   245
snej@3
   246
snej@3
   247
#if !TARGET_OS_IPHONE
snej@3
   248
snej@3
   249
- (NSData*) exportKeyInFormat: (SecExternalFormat)format 
snej@3
   250
                      withPEM: (BOOL)withPEM
snej@3
   251
                   alertTitle: (NSString*)title
snej@3
   252
                  alertPrompt: (NSString*)prompt
snej@3
   253
{
snej@3
   254
    SecKeyImportExportParameters params = {
snej@3
   255
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   256
        .flags = kSecKeySecurePassphrase,
snej@3
   257
        .alertTitle = (CFStringRef)title,
snej@3
   258
        .alertPrompt = (CFStringRef)prompt
snej@3
   259
    };
snej@3
   260
    CFDataRef data = NULL;
snej@3
   261
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   262
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   263
                                    &params, &data),
snej@3
   264
              @"SecKeychainItemExport"))
snej@3
   265
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   266
    else
snej@3
   267
        return nil;
snej@3
   268
}
snej@3
   269
snej@3
   270
- (NSData*) exportKey {
snej@3
   271
    return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
snej@3
   272
                        alertTitle: @"Export Private Key"
snej@3
   273
                       alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
snej@3
   274
            "You will need to re-enter the passphrase later when importing the key from this file, "
snej@3
   275
            "so keep it in a safe place."];
snej@3
   276
    //FIX: Should make these messages localizable.
snej@3
   277
}
snej@3
   278
snej@3
   279
snej@3
   280
- (NSData*) _exportKeyInFormat: (SecExternalFormat)format
snej@3
   281
                       withPEM: (BOOL)withPEM
snej@3
   282
                    passphrase: (NSString*)passphrase
snej@3
   283
{
snej@3
   284
    SecKeyImportExportParameters params = {
snej@3
   285
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   286
        .passphrase = (CFStringRef)passphrase
snej@3
   287
    };
snej@3
   288
    CFDataRef data = NULL;
snej@3
   289
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   290
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   291
                                    &params, &data),
snej@3
   292
              @"SecKeychainItemExport"))
snej@3
   293
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   294
    else
snej@3
   295
        return nil;
snej@3
   296
}
snej@3
   297
snej@13
   298
snej@13
   299
- (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
snej@13
   300
                       withAlgorithm: (CCAlgorithm)algorithm
snej@13
   301
                          sizeInBits: (unsigned)sizeInBits
snej@13
   302
{
snej@13
   303
    // First create a wrapped-key structure from the data:
snej@13
   304
    CSSM_WRAP_KEY wrappedKey = {
snej@13
   305
        .KeyHeader = {
snej@13
   306
            .BlobType = CSSM_KEYBLOB_WRAPPED,
snej@13
   307
            .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
snej@13
   308
            .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
snej@13
   309
            .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
snej@13
   310
            .LogicalKeySizeInBits = sizeInBits,
snej@13
   311
            .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
snej@13
   312
            .KeyUsage = CSSM_KEYUSE_ANY,
snej@13
   313
            .WrapAlgorithmId = self.cssmAlgorithm,
snej@13
   314
        },
snej@13
   315
        .KeyData = {
snej@13
   316
            .Data = (void*)wrappedData.bytes,
snej@13
   317
            .Length = wrappedData.length
snej@13
   318
        }
snej@13
   319
    };
snej@13
   320
        
snej@13
   321
    const CSSM_ACCESS_CREDENTIALS* credentials;
snej@13
   322
    credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
snej@13
   323
                                               type: kSecCredentialTypeDefault error: nil];
snej@13
   324
    CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
snej@13
   325
    CSSM_CC_HANDLE ctx;
snej@13
   326
    if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
snej@13
   327
                                                    self.cssmAlgorithm,
snej@13
   328
                                                    credentials, 
snej@13
   329
                                                    self.cssmKey,
snej@13
   330
                                                    CSSM_PADDING_PKCS1,
snej@13
   331
                                                    &ctx), 
snej@13
   332
                   @"CSSM_CSP_CreateAsymmetricContext"))
snej@13
   333
        return nil;
snej@13
   334
    
snej@13
   335
    // Now unwrap the key:
snej@13
   336
    MYSymmetricKey *result = nil;
snej@13
   337
    CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
snej@14
   338
    CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
snej@14
   339
    CSSM_DATA descriptiveData = {};
snej@13
   340
    if (checkcssm(CSSM_UnwrapKey(ctx, 
snej@13
   341
                                 self.cssmKey,
snej@13
   342
                                 &wrappedKey,
snej@13
   343
                                 wrappedKey.KeyHeader.KeyUsage,
snej@13
   344
                                 wrappedKey.KeyHeader.KeyAttr,
snej@14
   345
                                 &label,
snej@14
   346
                                 NULL,
snej@13
   347
                                 unwrappedKey,
snej@14
   348
                                 &descriptiveData),
snej@13
   349
                  @"CSSM_UnwrapKey")) {
snej@13
   350
        result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
snej@13
   351
    }
snej@13
   352
    // Finally, delete the context
snej@14
   353
    if (!result)
snej@14
   354
        free(unwrappedKey);
snej@13
   355
    CSSM_DeleteContext(ctx);
snej@13
   356
    return result;
snej@13
   357
}
snej@13
   358
snej@13
   359
snej@13
   360
#endif !TARGET_OS_IPHONE
snej@3
   361
snej@3
   362
@end
snej@14
   363
snej@14
   364
snej@14
   365
snej@14
   366
/*
snej@14
   367
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@14
   368
 
snej@14
   369
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@14
   370
 provided that the following conditions are met:
snej@14
   371
 
snej@14
   372
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@14
   373
 and the following disclaimer.
snej@14
   374
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@14
   375
 and the following disclaimer in the documentation and/or other materials provided with the
snej@14
   376
 distribution.
snej@14
   377
 
snej@14
   378
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@14
   379
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@14
   380
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@14
   381
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@14
   382
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@14
   383
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@14
   384
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@14
   385
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@14
   386
 */