]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | /* |
2 | * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. | |
3 | * | |
4 | * The contents of this file constitute Original Code as defined in and are | |
5 | * subject to the Apple Public Source License Version 1.2 (the 'License'). | |
6 | * You may not use this file except in compliance with the License. Please obtain | |
7 | * a copy of the License at http://www.apple.com/publicsource and read it before | |
8 | * using this file. | |
9 | * | |
10 | * This Original Code and all software distributed under the License are | |
11 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS | |
12 | * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT | |
13 | * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
14 | * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the | |
15 | * specific language governing rights and limitations under the License. | |
16 | */ | |
17 | ||
18 | ||
19 | /* | |
20 | File: deriveKey.cpp | |
21 | ||
22 | Contains: CSSM_DeriveKey functions | |
23 | ||
24 | Copyright: (C) 2000 by Apple Computer, Inc., all rights reserved | |
25 | ||
26 | Written by: Doug Mitchell <dmitch@apple.com> | |
27 | */ | |
28 | ||
29 | #include <HMACSHA1.h> | |
30 | #include <pbkdf2.h> | |
31 | #include <pbkdDigest.h> | |
32 | #include <pkcs12Derive.h> | |
33 | #include "AppleCSPSession.h" | |
34 | #include "AppleCSPUtils.h" | |
35 | #include "AppleCSPContext.h" | |
36 | #include "cspdebugging.h" | |
37 | #include <security_cdsa_utilities/context.h> | |
38 | #include <DH_exchange.h> | |
39 | #include "FEEAsymmetricContext.h" | |
40 | ||
41 | /* minimum legal values */ | |
42 | #define PBKDF2_MIN_SALT 8 /* bytes */ | |
43 | #define PBKDF2_MIN_ITER_CNT 1000 /* iteration count */ | |
44 | ||
45 | #define ALLOW_ZERO_PASSWORD 1 | |
46 | ||
47 | void AppleCSPSession::DeriveKey_PBKDF2( | |
48 | const Context &context, | |
49 | const CssmData &Param, | |
50 | CSSM_DATA *keyData) | |
51 | { | |
52 | /* validate algorithm-specific arguments */ | |
53 | ||
54 | /* Param must point to a CSSM_PKCS5_PBKDF2_PARAMS */ | |
55 | if(Param.Length != sizeof(CSSM_PKCS5_PBKDF2_PARAMS)) { | |
56 | errorLog0("DeriveKey_PBKDF2: Param wrong size\n"); | |
57 | CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER); | |
58 | } | |
59 | const CSSM_PKCS5_PBKDF2_PARAMS *pbkdf2Params = | |
60 | reinterpret_cast<const CSSM_PKCS5_PBKDF2_PARAMS *>(Param.Data); | |
61 | if(pbkdf2Params == NULL) { | |
62 | errorLog0("DeriveKey_PBKDF2: null Param.Data\n"); | |
63 | CssmError::throwMe(CSSMERR_CSP_INVALID_DATA); | |
64 | } | |
65 | ||
66 | /* Get passphrase from either baseKey or from CSSM_PKCS5_PBKDF2_PARAMS */ | |
67 | CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY); | |
68 | CSSM_SIZE passphraseLen = 0; | |
69 | uint8 *passphrase = NULL; | |
70 | if(passKey != NULL) { | |
71 | AppleCSPContext::symmetricKeyBits(context, *this, | |
72 | CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE, | |
73 | passphrase, passphraseLen); | |
74 | } | |
75 | else { | |
76 | passphraseLen = pbkdf2Params->Passphrase.Length; | |
77 | passphrase = pbkdf2Params->Passphrase.Data; | |
78 | } | |
79 | ||
80 | #if !ALLOW_ZERO_PASSWORD | |
81 | /* passphrase required */ | |
82 | if(passphrase == NULL) { | |
83 | errorLog0("DeriveKey_PBKDF2: null Passphrase\n"); | |
84 | CssmError::throwMe(CSSMERR_CSP_INVALID_DATA); | |
85 | } | |
86 | if(passphraseLen == 0) { | |
87 | /* FIXME - enforce minimum length? */ | |
88 | errorLog0("DeriveKey_PBKDF2: zero length passphrase\n"); | |
89 | CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER); | |
90 | } | |
91 | #endif /* ALLOW_ZERO_PASSWORD */ | |
92 | ||
93 | if(pbkdf2Params->PseudoRandomFunction != | |
94 | CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1) { | |
95 | errorLog0("DeriveKey_PBKDF2: invalid PRF\n"); | |
96 | CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); | |
97 | } | |
98 | ||
99 | /* salt, from context, required */ | |
100 | CssmData salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT, | |
101 | CSSMERR_CSP_MISSING_ATTR_SALT); | |
102 | if((salt.Data == NULL) || (salt.Length < PBKDF2_MIN_SALT)){ | |
103 | CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SALT); | |
104 | } | |
105 | ||
106 | /* iteration count, from context, required */ | |
107 | uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT, | |
108 | CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT); | |
109 | if(iterCount < PBKDF2_MIN_ITER_CNT) { | |
110 | CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_ITERATION_COUNT); | |
111 | } | |
112 | ||
113 | /* | |
114 | * allocate a temp buffer, length | |
115 | * = MAX (hLen, saltLen + 4) + 2 * hLen | |
116 | * = MAX (kSHA1DigestSize, saltLen + 4) + 2 * kSHA1DigestSize | |
117 | */ | |
427c49bc | 118 | size_t tempLen = salt.Length + 4; |
b1ab9ed8 A |
119 | if(tempLen < kSHA1DigestSize) { |
120 | tempLen = kSHA1DigestSize; | |
121 | } | |
122 | tempLen += (2 * kSHA1DigestSize); | |
123 | CSSM_DATA tempData = {0, NULL}; | |
124 | setUpData(tempData, tempLen, privAllocator); | |
125 | ||
126 | /* go */ | |
127 | pbkdf2 (hmacsha1, | |
128 | kSHA1DigestSize, | |
427c49bc A |
129 | passphrase, (uint32)passphraseLen, |
130 | salt.Data, (uint32)salt.Length, | |
b1ab9ed8 | 131 | iterCount, |
427c49bc | 132 | keyData->Data, (uint32)keyData->Length, |
b1ab9ed8 A |
133 | tempData.Data); |
134 | freeData(&tempData, privAllocator, false); | |
135 | } | |
136 | ||
137 | /* | |
138 | * PKCS5 v1.5 key derivation. Also used for traditional openssl key | |
139 | * derivation, which is mighty similar to PKCS5 v1.5, with the addition | |
140 | * of the ability to generate more than (keysize + ivsize) bytes. | |
141 | */ | |
142 | void AppleCSPSession::DeriveKey_PKCS5_V1_5( | |
143 | const Context &context, | |
144 | CSSM_ALGORITHMS algId, | |
145 | const CssmData &Param, // IV optional, mallocd by app to indicate | |
146 | // size | |
147 | CSSM_DATA *keyData) // mallocd by caller to indicate size | |
148 | { | |
149 | CSSM_DATA pwd = {0, NULL}; | |
150 | ||
151 | /* password from either Seed.Param or from base key */ | |
152 | CssmCryptoData *cryptData = | |
153 | context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED); | |
154 | if((cryptData != NULL) && (cryptData->Param.Length != 0)) { | |
155 | pwd = cryptData->Param; | |
156 | } | |
157 | else { | |
158 | /* Get secure passphrase from base key */ | |
159 | CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY); | |
160 | if (passKey != NULL) { | |
161 | AppleCSPContext::symmetricKeyBits(context, *this, | |
162 | CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE, | |
163 | pwd.Data, pwd.Length); | |
164 | } | |
165 | } | |
166 | ||
167 | if(pwd.Data == NULL) { | |
168 | errorLog0("DeriveKey_PKCS5_V1_5: null Passphrase\n"); | |
169 | CssmError::throwMe(CSSMERR_CSP_INVALID_DATA); | |
170 | } | |
171 | if(pwd.Length == 0) { | |
172 | errorLog0("DeriveKey_PKCS5_V1_5: zero length passphrase\n"); | |
173 | CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER); | |
174 | } | |
175 | ||
176 | CSSM_ALGORITHMS hashAlg; | |
177 | unsigned digestLen; | |
178 | bool opensslAlg = false; | |
179 | switch(algId) { | |
180 | case CSSM_ALGID_PKCS5_PBKDF1_MD5: | |
181 | hashAlg = CSSM_ALGID_MD5; | |
182 | digestLen = kMD5DigestSize; | |
183 | break; | |
184 | case CSSM_ALGID_PKCS5_PBKDF1_MD2: | |
185 | hashAlg = CSSM_ALGID_MD2; | |
186 | digestLen = kMD2DigestSize; | |
187 | break; | |
188 | case CSSM_ALGID_PKCS5_PBKDF1_SHA1: | |
189 | hashAlg = CSSM_ALGID_SHA1; | |
190 | digestLen = kSHA1DigestSize; | |
191 | break; | |
192 | case CSSM_ALGID_PBE_OPENSSL_MD5: | |
193 | hashAlg = CSSM_ALGID_MD5; | |
194 | digestLen = kMD5DigestSize; | |
195 | opensslAlg = true; | |
196 | break; | |
197 | default: | |
198 | /* should not have been called */ | |
199 | assert(0); | |
200 | CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); | |
201 | } | |
202 | ||
203 | /* IV optional */ | |
204 | CSSM_DATA iv = Param; | |
205 | ||
206 | /* total requested length can't exceed digest size for struct PKCS5 v1.5*/ | |
207 | if(!opensslAlg && ((keyData->Length + iv.Length) > digestLen)) { | |
208 | errorLog0("DeriveKey_PKCS5_V1_5: requested length larger than digest\n"); | |
209 | CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY_LENGTH); | |
210 | } | |
211 | ||
212 | /* salt, from context, required */ | |
213 | CssmData salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT, | |
214 | CSSMERR_CSP_MISSING_ATTR_SALT); | |
215 | if(salt.Data == NULL) { | |
216 | CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SALT); | |
217 | } | |
218 | ||
219 | /* iteration count, from context, required */ | |
220 | uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT, | |
221 | CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT); | |
222 | ||
223 | /* | |
224 | * Apply the underlying hash function Hash for c iterations to | |
225 | * the concatenation of the password P and the salt S, then | |
226 | * extract the first dkLen octets to produce a derived key DK: | |
227 | * | |
228 | * T1 = Hash (P || S) , | |
229 | * T2 = Hash (T1) , | |
230 | * ... | |
231 | * Tc = Hash (Tc-1) , | |
232 | * DK = Tc<0..dkLen-1> . | |
233 | */ | |
234 | DigestCtx ctx; | |
235 | uint8 *keyDataP = keyData->Data; | |
427c49bc | 236 | size_t keyBytesToGo = keyData->Length; |
b1ab9ed8 | 237 | uint8 *ivDataP = iv.Data; |
427c49bc | 238 | size_t ivBytesToGo = iv.Length; |
b1ab9ed8 A |
239 | bool looping = false; // true for additional bytes for openssl |
240 | unsigned char digestOut[kMaxDigestSize]; | |
241 | ||
242 | for(;;) { | |
243 | /* this loop guaranteed to only run once if !opensslAlg */ | |
244 | DigestCtxInit(&ctx, hashAlg); | |
245 | ||
246 | if(looping) { | |
247 | /* openssl addition: re-digest the digest here */ | |
248 | DigestCtxUpdate(&ctx, digestOut, digestLen); | |
249 | } | |
250 | ||
251 | /* digest password then salt */ | |
427c49bc A |
252 | DigestCtxUpdate(&ctx, pwd.Data, (uint32)pwd.Length); |
253 | DigestCtxUpdate(&ctx, salt.Data, (uint32)salt.Length); | |
b1ab9ed8 A |
254 | |
255 | DigestCtxFinal(&ctx, digestOut); | |
256 | ||
257 | /* now iterCount-1 more iterations */ | |
258 | for(unsigned dex=1; dex<iterCount; dex++) { | |
259 | DigestCtxInit(&ctx, hashAlg); | |
260 | DigestCtxUpdate(&ctx, digestOut, digestLen); | |
261 | DigestCtxFinal(&ctx, digestOut); | |
262 | } | |
263 | ||
264 | /* first n bytes to the key */ | |
265 | uint32 bytesAvail = digestLen; | |
427c49bc | 266 | size_t toMove = (keyBytesToGo > bytesAvail) ? bytesAvail : keyBytesToGo; |
b1ab9ed8 A |
267 | memmove(keyDataP, digestOut, toMove); |
268 | uint8 *remainder = digestOut + toMove; | |
269 | bytesAvail -= toMove; | |
270 | keyDataP += toMove; | |
271 | keyBytesToGo -= toMove; | |
272 | ||
273 | /* then optionally some to IV */ | |
274 | if(ivBytesToGo && bytesAvail) { | |
275 | toMove = (ivBytesToGo > bytesAvail) ? bytesAvail : ivBytesToGo; | |
276 | memmove(ivDataP, remainder, toMove); | |
277 | ivDataP += toMove; | |
278 | ivBytesToGo -= toMove; | |
279 | } | |
280 | if((keyBytesToGo == 0) && (ivBytesToGo == 0)) { | |
281 | /* guaranteed true for PKCS5 v1.5 */ | |
282 | break; | |
283 | } | |
284 | ||
285 | assert(opensslAlg == true); | |
286 | looping = true; | |
287 | } | |
288 | DigestCtxFree(&ctx); | |
289 | } | |
290 | ||
291 | /* | |
292 | * Member function initially declared for CSPAbstractPluginSession; | |
293 | * we're overriding the null version in CSPFullPluginSession. | |
294 | * | |
295 | * We'll generate any type of key (for now). | |
296 | */ | |
297 | void AppleCSPSession::DeriveKey( | |
298 | CSSM_CC_HANDLE CCHandle, | |
299 | const Context &context, | |
300 | CssmData &Param, | |
301 | uint32 KeyUsage, | |
302 | uint32 KeyAttr, | |
303 | const CssmData *KeyLabel, | |
304 | const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry, | |
305 | CssmKey &DerivedKey) | |
306 | { | |
307 | /* validate input args, common to all algorithms */ | |
308 | switch(context.algorithm()) { | |
309 | case CSSM_ALGID_PKCS5_PBKDF2: | |
310 | case CSSM_ALGID_DH: | |
311 | case CSSM_ALGID_PKCS12_PBE_ENCR: | |
312 | case CSSM_ALGID_PKCS12_PBE_MAC: | |
313 | case CSSM_ALGID_PKCS5_PBKDF1_MD5: | |
314 | case CSSM_ALGID_PKCS5_PBKDF1_MD2: | |
315 | case CSSM_ALGID_PKCS5_PBKDF1_SHA1: | |
316 | case CSSM_ALGID_PBE_OPENSSL_MD5: | |
317 | case CSSM_ALGID_OPENSSH1: | |
318 | #if CRYPTKIT_CSP_ENABLE | |
319 | case CSSM_ALGID_ECDH: | |
320 | case CSSM_ALGID_ECDH_X963_KDF: | |
321 | #endif | |
322 | break; | |
323 | /* maybe more here, later */ | |
324 | default: | |
325 | CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); | |
326 | } | |
327 | DerivedKey.KeyData.Data = NULL; | |
328 | DerivedKey.KeyData.Length = 0; | |
329 | cspKeyStorage keyStorage = cspParseKeyAttr(CKT_Session, KeyAttr); | |
330 | cspValidateKeyUsageBits(CKT_Session, KeyUsage); | |
331 | ||
332 | /* outgoing key type, required (though any algorithm is OK) */ | |
333 | uint32 keyType = context.getInt(CSSM_ATTRIBUTE_KEY_TYPE, | |
334 | CSSMERR_CSP_MISSING_ATTR_KEY_TYPE); | |
335 | ||
336 | /* outgoing key size, required - any nonzero value is OK */ | |
337 | uint32 reqKeySize = context.getInt( | |
338 | CSSM_ATTRIBUTE_KEY_LENGTH, | |
339 | CSSMERR_CSP_MISSING_ATTR_KEY_LENGTH); | |
340 | ||
341 | /* cook up a place to put the key data */ | |
342 | uint32 keySizeInBytes = (reqKeySize + 7) / 8; | |
343 | SymmetricBinaryKey *binKey = NULL; | |
344 | CSSM_DATA_PTR keyData = NULL; | |
345 | ||
346 | switch(keyStorage) { | |
347 | case CKS_None: | |
348 | /* no way */ | |
349 | CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK); | |
350 | case CKS_Ref: | |
351 | /* cook up a symmetric binary key */ | |
352 | binKey = new SymmetricBinaryKey(reqKeySize); | |
353 | keyData = &binKey->mKeyData; | |
354 | break; | |
355 | case CKS_Data: | |
356 | /* key bytes --> caller's cssmKey */ | |
357 | keyData = &DerivedKey.KeyData; | |
358 | setUpData(*keyData, keySizeInBytes, | |
359 | normAllocator); | |
360 | break; | |
361 | } | |
362 | ||
363 | /* break off to algorithm-specific code, whose job it is | |
364 | * to fill in keyData->Data with keyData->Length bytes */ | |
365 | switch(context.algorithm()) { | |
366 | case CSSM_ALGID_PKCS5_PBKDF2: | |
367 | DeriveKey_PBKDF2(context, | |
368 | Param, | |
369 | keyData); | |
370 | break; | |
371 | case CSSM_ALGID_DH: | |
372 | DeriveKey_DH(context, | |
373 | Param, | |
374 | keyData, | |
375 | *this); | |
376 | break; | |
377 | case CSSM_ALGID_PKCS12_PBE_ENCR: | |
378 | case CSSM_ALGID_PKCS12_PBE_MAC: | |
379 | DeriveKey_PKCS12(context, | |
380 | *this, | |
381 | Param, | |
382 | keyData); | |
383 | break; | |
384 | case CSSM_ALGID_PKCS5_PBKDF1_MD5: | |
385 | case CSSM_ALGID_PKCS5_PBKDF1_MD2: | |
386 | case CSSM_ALGID_PKCS5_PBKDF1_SHA1: | |
387 | case CSSM_ALGID_PBE_OPENSSL_MD5: | |
388 | DeriveKey_PKCS5_V1_5(context, | |
389 | context.algorithm(), | |
390 | Param, | |
391 | keyData); | |
392 | break; | |
393 | case CSSM_ALGID_OPENSSH1: | |
394 | DeriveKey_OpenSSH1(context, | |
395 | context.algorithm(), | |
396 | Param, | |
397 | keyData); | |
398 | break; | |
399 | #if CRYPTKIT_CSP_ENABLE | |
400 | case CSSM_ALGID_ECDH: | |
401 | case CSSM_ALGID_ECDH_X963_KDF: | |
402 | CryptKit::DeriveKey_ECDH(context, | |
403 | context.algorithm(), | |
404 | Param, | |
405 | keyData, | |
406 | *this); | |
407 | break; | |
408 | #endif | |
409 | /* maybe more here, later */ | |
410 | default: | |
411 | assert(0); | |
412 | } | |
413 | ||
414 | /* set up outgoing header */ | |
415 | KeyAttr &= ~KEY_ATTR_RETURN_MASK; | |
416 | CSSM_KEYHEADER &hdr = DerivedKey.KeyHeader; | |
417 | setKeyHeader(hdr, | |
418 | plugin.myGuid(), | |
419 | keyType, | |
420 | CSSM_KEYCLASS_SESSION_KEY, | |
421 | KeyAttr, | |
422 | KeyUsage); | |
423 | /* handle derived size < requested size, legal for Diffie-Hellman */ | |
427c49bc | 424 | hdr.LogicalKeySizeInBits = (uint32)(keyData->Length * 8); |
b1ab9ed8 A |
425 | |
426 | if(keyStorage == CKS_Ref) { | |
427 | /* store and convert to ref key */ | |
428 | addRefKey(*binKey, DerivedKey); | |
429 | } | |
430 | else { | |
431 | /* Raw data */ | |
432 | hdr.BlobType = CSSM_KEYBLOB_RAW; | |
433 | hdr.Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; | |
434 | } | |
435 | } | |
436 |