MYPrivateKey.m
author snej@snej.local
Sun Apr 12 22:16:14 2009 -0700 (2009-04-12)
changeset 9 aa5eb3fd6ebf
parent 5 b2e360b78189
child 13 6fd9177eb6da
permissions -rw-r--r--
Doc touch-up
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@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@3
   216
- (NSData*) decryptData: (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@3
   303
#endif TARGET_OS_IPHONE
snej@3
   304
snej@3
   305
@end