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,¶ms);
|
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,¶ms);
|
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 |
¶ms, &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 |
¶ms, &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
|