MYPrivateKey.m
author snej@snej.local
Thu Apr 09 22:46:48 2009 -0700 (2009-04-09)
changeset 6 2d7692f9b6b4
parent 4 f4709533c816
child 8 4c0eafa7b233
permissions -rw-r--r--
Updated the README for the 0.1 release.
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@3
   156
                           CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY,        // public key
snej@3
   157
                           CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
snej@3
   158
                           CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN,          // private key
snej@3
   159
                           CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_PERMANENT,
snej@3
   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@3
   175
    return $sprintf(@"%@[%@]", [self class], self.publicKeyDigest.abbreviatedHexString);
snej@3
   176
}
snej@3
   177
snej@3
   178
@synthesize publicKey=_publicKey;
snej@3
   179
snej@3
   180
- (MYSHA1Digest*) publicKeyDigest {
snej@3
   181
    return _publicKey.publicKeyDigest;
snej@3
   182
}
snej@3
   183
snej@3
   184
- (SecExternalItemType) keyType {
snej@3
   185
#if MYCRYPTO_USE_IPHONE_API
snej@3
   186
    return kSecAttrKeyClassPublic;
snej@3
   187
#else
snej@3
   188
    return kSecItemTypePrivateKey;
snej@3
   189
#endif
snej@3
   190
}
snej@3
   191
snej@3
   192
- (NSData *) keyData {
snej@3
   193
    [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
snej@3
   194
    return nil;
snej@3
   195
}
snej@3
   196
snej@3
   197
- (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
snej@3
   198
    return [super setValue: valueStr ofAttribute: attr]
snej@3
   199
        && [_publicKey setValue: valueStr ofAttribute: attr];
snej@3
   200
}
snej@3
   201
snej@3
   202
snej@3
   203
#pragma mark -
snej@3
   204
#pragma mark OPERATIONS:
snej@3
   205
snej@3
   206
snej@3
   207
- (BOOL) removeFromKeychain {
snej@3
   208
    return [super removeFromKeychain]
snej@3
   209
        && [_publicKey removeFromKeychain];
snej@3
   210
}
snej@3
   211
snej@3
   212
snej@3
   213
- (NSData*) decryptData: (NSData*)data {
snej@3
   214
    return [self _crypt: data operation: NO];
snej@3
   215
}
snej@3
   216
snej@3
   217
snej@3
   218
- (NSData*) signData: (NSData*)data {
snej@3
   219
    Assert(data);
snej@3
   220
#if MYCRYPTO_USE_IPHONE_API
snej@3
   221
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
snej@3
   222
    CC_SHA1(data.bytes,data.length, digest);
snej@3
   223
snej@3
   224
    size_t sigLen = 1024;
snej@3
   225
    uint8_t sigBuf[sigLen];
snej@3
   226
    OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
snej@3
   227
                                 digest,sizeof(digest), //data.bytes, data.length,
snej@3
   228
                                 sigBuf, &sigLen);
snej@3
   229
    if(err) {
snej@3
   230
        Warn(@"SecKeyRawSign failed: %i",err);
snej@3
   231
        return nil;
snej@3
   232
    } else
snej@3
   233
        return [NSData dataWithBytes: sigBuf length: sigLen];
snej@3
   234
#else
snej@3
   235
    NSData *signature = nil;
snej@3
   236
    CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA256WithRSA];
snej@3
   237
    if (!ccHandle) return nil;
snej@3
   238
    CSSM_DATA original = {data.length, (void*)data.bytes};
snej@3
   239
    CSSM_DATA result = {0,NULL};
snej@3
   240
    if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
snej@3
   241
        signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
snej@3
   242
                                   freeWhenDone: YES];
snej@3
   243
    CSSM_DeleteContext(ccHandle);
snej@3
   244
    return signature;
snej@3
   245
#endif
snej@3
   246
}
snej@3
   247
snej@3
   248
snej@3
   249
#if !TARGET_OS_IPHONE
snej@3
   250
snej@3
   251
- (NSData*) exportKeyInFormat: (SecExternalFormat)format 
snej@3
   252
                      withPEM: (BOOL)withPEM
snej@3
   253
                   alertTitle: (NSString*)title
snej@3
   254
                  alertPrompt: (NSString*)prompt
snej@3
   255
{
snej@3
   256
    SecKeyImportExportParameters params = {
snej@3
   257
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   258
        .flags = kSecKeySecurePassphrase,
snej@3
   259
        .alertTitle = (CFStringRef)title,
snej@3
   260
        .alertPrompt = (CFStringRef)prompt
snej@3
   261
    };
snej@3
   262
    CFDataRef data = NULL;
snej@3
   263
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   264
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   265
                                    &params, &data),
snej@3
   266
              @"SecKeychainItemExport"))
snej@3
   267
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   268
    else
snej@3
   269
        return nil;
snej@3
   270
}
snej@3
   271
snej@3
   272
- (NSData*) exportKey {
snej@3
   273
    return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
snej@3
   274
                        alertTitle: @"Export Private Key"
snej@3
   275
                       alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
snej@3
   276
            "You will need to re-enter the passphrase later when importing the key from this file, "
snej@3
   277
            "so keep it in a safe place."];
snej@3
   278
    //FIX: Should make these messages localizable.
snej@3
   279
}
snej@3
   280
snej@3
   281
snej@3
   282
- (NSData*) _exportKeyInFormat: (SecExternalFormat)format
snej@3
   283
                       withPEM: (BOOL)withPEM
snej@3
   284
                    passphrase: (NSString*)passphrase
snej@3
   285
{
snej@3
   286
    SecKeyImportExportParameters params = {
snej@3
   287
        .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3
   288
        .passphrase = (CFStringRef)passphrase
snej@3
   289
    };
snej@3
   290
    CFDataRef data = NULL;
snej@3
   291
    if (check(SecKeychainItemExport(self.keyRef,
snej@3
   292
                                    format, (withPEM ?kSecItemPemArmour :0), 
snej@3
   293
                                    &params, &data),
snej@3
   294
              @"SecKeychainItemExport"))
snej@3
   295
        return [(id)CFMakeCollectable(data) autorelease];
snej@3
   296
    else
snej@3
   297
        return nil;
snej@3
   298
}
snej@3
   299
snej@3
   300
#endif TARGET_OS_IPHONE
snej@3
   301
snej@3
   302
@end