MYPrivateKey.m
author snej@snej.local
Sun Apr 19 21:19:35 2009 -0700 (2009-04-19)
changeset 14 3af1d1c0ceb5
parent 13 6fd9177eb6da
child 17 90a70925562b
permissions -rw-r--r--
* Some cleanup. Got the test cases to pass again.
* Added some missing copyright notices.
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@4
   121
- (MYIdentity*) createSelfSignedIdentityWithAttributes: (NSDictionary*)attributes {
snej@4
   122
    return MYIdentityCreateSelfSigned(self, attributes);
snej@4
   123
}
snej@4
   124
snej@4
   125
snej@3
   126
#endif
snej@3
   127
snej@3
   128
snej@3
   129
- (void) dealloc
snej@3
   130
{
snej@3
   131
    [_publicKey release];
snej@3
   132
    [super dealloc];
snej@3
   133
}
snej@3
   134
snej@3
   135
snej@3
   136
+ (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
snej@3
   137
                                 inKeychain: (MYKeychain*)keychain 
snej@3
   138
{
snej@3
   139
    Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
snej@3
   140
    SecKeyRef pubKey=NULL, privKey=NULL;
snej@3
   141
    OSStatus err;
snej@3
   142
    
snej@3
   143
#if MYCRYPTO_USE_IPHONE_API
snej@3
   144
    NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
snej@3
   145
    NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
snej@3
   146
    NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
snej@3
   147
                                    {(id)kSecAttrKeySizeInBits, $object(keySize)},
snej@3
   148
                                    {(id)kSecPublicKeyAttrs, pubKeyAttrs},
snej@3
   149
                                    {(id)kSecPrivateKeyAttrs, privKeyAttrs} );
snej@3
   150
    err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
snej@3
   151
#else
snej@3
   152
    err = SecKeyCreatePair(keychain.keychainRefOrDefault,
snej@3
   153
                           CSSM_ALGID_RSA, 
snej@3
   154
                           keySize,
snej@3
   155
                           0LL,
snej@13
   156
                           CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,        // public key
snej@3
   157
                           CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
snej@8
   158
                           CSSM_KEYUSE_ANY,                                 // private key
snej@8
   159
                           CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
snej@8
   160
                           NULL,                                            // SecAccessRef
snej@3
   161
                           &pubKey, &privKey);
snej@3
   162
#endif
snej@3
   163
    if (!check(err, @"SecKeyCreatePair")) {
snej@3
   164
        return nil;
snej@3
   165
    } else
snej@3
   166
        return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
snej@3
   167
}
snej@3
   168
snej@3
   169
snej@3
   170
#pragma mark -
snej@3
   171
#pragma mark ACCESSORS:
snej@3
   172
snej@3
   173
snej@3
   174
- (NSString*) description {
snej@8
   175
    return $sprintf(@"%@[%@ %@ /%p]", [self class], 
snej@8
   176
                    self.publicKeyDigest.abbreviatedHexString,
snej@8
   177
                    (self.name ?:@""),
snej@8
   178
                    self.keychainItemRef);
snej@3
   179
}
snej@3
   180
snej@3
   181
@synthesize publicKey=_publicKey;
snej@3
   182
snej@3
   183
- (MYSHA1Digest*) publicKeyDigest {
snej@3
   184
    return _publicKey.publicKeyDigest;
snej@3
   185
}
snej@3
   186
snej@3
   187
- (SecExternalItemType) keyType {
snej@3
   188
#if MYCRYPTO_USE_IPHONE_API
snej@3
   189
    return kSecAttrKeyClassPublic;
snej@3
   190
#else
snej@3
   191
    return kSecItemTypePrivateKey;
snej@3
   192
#endif
snej@3
   193
}
snej@3
   194
snej@3
   195
- (NSData *) keyData {
snej@3
   196
    [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
snej@3
   197
    return nil;
snej@3
   198
}
snej@3
   199
snej@3
   200
- (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
snej@3
   201
    return [super setValue: valueStr ofAttribute: attr]
snej@3
   202
        && [_publicKey setValue: valueStr ofAttribute: attr];
snej@3
   203
}
snej@3
   204
snej@3
   205
snej@3
   206
#pragma mark -
snej@3
   207
#pragma mark OPERATIONS:
snej@3
   208
snej@3
   209
snej@3
   210
- (BOOL) removeFromKeychain {
snej@3
   211
    return [super removeFromKeychain]
snej@3
   212
        && [_publicKey removeFromKeychain];
snej@3
   213
}
snej@3
   214
snej@3
   215
snej@13
   216
- (NSData*) rawDecryptData: (NSData*)data {
snej@3
   217
    return [self _crypt: data operation: NO];
snej@3
   218
}
snej@3
   219
snej@3
   220
snej@3
   221
- (NSData*) signData: (NSData*)data {
snej@3
   222
    Assert(data);
snej@3
   223
#if MYCRYPTO_USE_IPHONE_API
snej@3
   224
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
snej@3
   225
    CC_SHA1(data.bytes,data.length, digest);
snej@3
   226
snej@3
   227
    size_t sigLen = 1024;
snej@3
   228
    uint8_t sigBuf[sigLen];
snej@3
   229
    OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
snej@3
   230
                                 digest,sizeof(digest), //data.bytes, data.length,
snej@3
   231
                                 sigBuf, &sigLen);
snej@3
   232
    if(err) {
snej@3
   233
        Warn(@"SecKeyRawSign failed: %i",err);
snej@3
   234
        return nil;
snej@3
   235
    } else
snej@3
   236
        return [NSData dataWithBytes: sigBuf length: sigLen];
snej@3
   237
#else
snej@3
   238
    NSData *signature = nil;
snej@3
   239
    CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA256WithRSA];
snej@3
   240
    if (!ccHandle) return nil;
snej@3
   241
    CSSM_DATA original = {data.length, (void*)data.bytes};
snej@3
   242
    CSSM_DATA result = {0,NULL};
snej@3
   243
    if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
snej@3
   244
        signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
snej@3
   245
                                   freeWhenDone: YES];
snej@3
   246
    CSSM_DeleteContext(ccHandle);
snej@3
   247
    return signature;
snej@3
   248
#endif
snej@3
   249
}
snej@3
   250
snej@3
   251
snej@3
   252
#if !TARGET_OS_IPHONE
snej@3
   253
snej@3
   254
- (NSData*) exportKeyInFormat: (SecExternalFormat)format 
snej@3
   255
                      withPEM: (BOOL)withPEM
snej@3
   256
                   alertTitle: (NSString*)title
snej@3
   257
                  alertPrompt: (NSString*)prompt
snej@3
   258
{
snej@3
   259
    SecKeyImportExportParameters params = {
snej@3
   260
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   261
        .flags = kSecKeySecurePassphrase,
snej@3
   262
        .alertTitle = (CFStringRef)title,
snej@3
   263
        .alertPrompt = (CFStringRef)prompt
snej@3
   264
    };
snej@3
   265
    CFDataRef data = NULL;
snej@3
   266
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   267
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   268
                                    &params, &data),
snej@3
   269
              @"SecKeychainItemExport"))
snej@3
   270
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   271
    else
snej@3
   272
        return nil;
snej@3
   273
}
snej@3
   274
snej@3
   275
- (NSData*) exportKey {
snej@3
   276
    return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
snej@3
   277
                        alertTitle: @"Export Private Key"
snej@3
   278
                       alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
snej@3
   279
            "You will need to re-enter the passphrase later when importing the key from this file, "
snej@3
   280
            "so keep it in a safe place."];
snej@3
   281
    //FIX: Should make these messages localizable.
snej@3
   282
}
snej@3
   283
snej@3
   284
snej@3
   285
- (NSData*) _exportKeyInFormat: (SecExternalFormat)format
snej@3
   286
                       withPEM: (BOOL)withPEM
snej@3
   287
                    passphrase: (NSString*)passphrase
snej@3
   288
{
snej@3
   289
    SecKeyImportExportParameters params = {
snej@3
   290
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   291
        .passphrase = (CFStringRef)passphrase
snej@3
   292
    };
snej@3
   293
    CFDataRef data = NULL;
snej@3
   294
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   295
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   296
                                    &params, &data),
snej@3
   297
              @"SecKeychainItemExport"))
snej@3
   298
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   299
    else
snej@3
   300
        return nil;
snej@3
   301
}
snej@3
   302
snej@13
   303
snej@13
   304
- (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
snej@13
   305
                       withAlgorithm: (CCAlgorithm)algorithm
snej@13
   306
                          sizeInBits: (unsigned)sizeInBits
snej@13
   307
{
snej@13
   308
    // First create a wrapped-key structure from the data:
snej@13
   309
    CSSM_WRAP_KEY wrappedKey = {
snej@13
   310
        .KeyHeader = {
snej@13
   311
            .BlobType = CSSM_KEYBLOB_WRAPPED,
snej@13
   312
            .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
snej@13
   313
            .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
snej@13
   314
            .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
snej@13
   315
            .LogicalKeySizeInBits = sizeInBits,
snej@13
   316
            .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
snej@13
   317
            .KeyUsage = CSSM_KEYUSE_ANY,
snej@13
   318
            .WrapAlgorithmId = self.cssmAlgorithm,
snej@13
   319
        },
snej@13
   320
        .KeyData = {
snej@13
   321
            .Data = (void*)wrappedData.bytes,
snej@13
   322
            .Length = wrappedData.length
snej@13
   323
        }
snej@13
   324
    };
snej@13
   325
        
snej@13
   326
    const CSSM_ACCESS_CREDENTIALS* credentials;
snej@13
   327
    credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
snej@13
   328
                                               type: kSecCredentialTypeDefault error: nil];
snej@13
   329
    CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
snej@13
   330
    CSSM_CC_HANDLE ctx;
snej@13
   331
    if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
snej@13
   332
                                                    self.cssmAlgorithm,
snej@13
   333
                                                    credentials, 
snej@13
   334
                                                    self.cssmKey,
snej@13
   335
                                                    CSSM_PADDING_PKCS1,
snej@13
   336
                                                    &ctx), 
snej@13
   337
                   @"CSSM_CSP_CreateAsymmetricContext"))
snej@13
   338
        return nil;
snej@13
   339
    
snej@13
   340
    // Now unwrap the key:
snej@13
   341
    MYSymmetricKey *result = nil;
snej@13
   342
    CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
snej@14
   343
    CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
snej@14
   344
    CSSM_DATA descriptiveData = {};
snej@13
   345
    if (checkcssm(CSSM_UnwrapKey(ctx, 
snej@13
   346
                                 self.cssmKey,
snej@13
   347
                                 &wrappedKey,
snej@13
   348
                                 wrappedKey.KeyHeader.KeyUsage,
snej@13
   349
                                 wrappedKey.KeyHeader.KeyAttr,
snej@14
   350
                                 &label,
snej@14
   351
                                 NULL,
snej@13
   352
                                 unwrappedKey,
snej@14
   353
                                 &descriptiveData),
snej@13
   354
                  @"CSSM_UnwrapKey")) {
snej@13
   355
        result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
snej@13
   356
    }
snej@13
   357
    // Finally, delete the context
snej@14
   358
    if (!result)
snej@14
   359
        free(unwrappedKey);
snej@13
   360
    CSSM_DeleteContext(ctx);
snej@13
   361
    return result;
snej@13
   362
}
snej@13
   363
snej@13
   364
snej@13
   365
#endif !TARGET_OS_IPHONE
snej@3
   366
snej@3
   367
@end
snej@14
   368
snej@14
   369
snej@14
   370
snej@14
   371
/*
snej@14
   372
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@14
   373
 
snej@14
   374
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@14
   375
 provided that the following conditions are met:
snej@14
   376
 
snej@14
   377
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@14
   378
 and the following disclaimer.
snej@14
   379
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@14
   380
 and the following disclaimer in the documentation and/or other materials provided with the
snej@14
   381
 distribution.
snej@14
   382
 
snej@14
   383
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@14
   384
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@14
   385
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@14
   386
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@14
   387
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@14
   388
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@14
   389
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@14
   390
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@14
   391
 */