2 * Copyright (c) 2003-2004,2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
28 #include "pkcs12Utils.h"
30 #include "pkcs7Templates.h"
31 #include "pkcs12Templates.h"
32 #include "pkcs12Crypto.h"
33 #include "pkcs12Debug.h"
34 #include <security_asn1/nssUtils.h>
35 #include <Security/secasn1t.h>
36 #include <security_utilities/devrandom.h>
37 #include <security_utilities/errors.h>
38 #include <security_cdsa_utils/cuCdsaUtils.h>
39 #include <Security/oidsattr.h>
40 #include <Security/oidsalg.h>
41 #include <Security/cssmapple.h>
43 /* malloc a NULL-ed array of pointers of size num+1 */
44 void **p12NssNullArray(
48 unsigned len
= (num
+ 1) * sizeof(void *);
49 void **p
= (void **)coder
.malloc(len
);
54 /* CSSM_DATA --> uint32. Returns true if OK. */
56 const CSSM_DATA
&cdata
,
59 if((cdata
.Length
== 0) || (cdata
.Data
== NULL
)) {
60 /* default/not present */
64 CSSM_SIZE len
= cdata
.Length
;
65 if(len
> sizeof(uint32
)) {
70 uint8
*cp
= cdata
.Data
;
71 for(uint32 i
=0; i
<len
; i
++) {
72 rtn
= (rtn
<< 8) | *cp
++;
78 /* uint32 --> CSSM_DATA */
89 else if(num
< 0x10000) {
92 else if(num
< 0x1000000) {
98 coder
.allocItem(cdata
, len
);
99 uint8
*cp
= &cdata
.Data
[len
- 1];
100 for(unsigned i
=0; i
<len
; i
++) {
106 /* CFDataRef <--> CSSM_DATA */
107 CFDataRef
p12CssmDataToCf(
110 return CFDataCreate(NULL
, c
.Data
, c
.Length
);
113 void p12CfDataToCssm(
118 coder
.allocCopyItem(CFDataGetBytePtr(cf
),
119 CFDataGetLength(cf
), c
);
123 * Attempt to convert a CFStringRef, which represents a SafeBag's
124 * FriendlyName, to a UTF8-encoded CSSM_DATA. The CSSM_DATA and its
125 * referent are allocated in the specified SecNssCoder's memory.
126 * No guarantee that this conversion works. If it doesn't we return
127 * NULL and caller must be prepared to deal with that.
129 CSSM_DATA_PTR
p12StringToUtf8(
138 CFRange range
= { 0, CFStringGetLength(cfStr
) };
139 CFStringGetBytes(cfStr
, range
, kCFStringEncodingUTF8
, 0, FALSE
, NULL
, 0, &strLen
);
144 CSSM_DATA_PTR rtn
= coder
.mallocn
<CSSM_DATA
>();
145 coder
.allocItem(*rtn
, strLen
);
147 if(!CFStringGetBytes(cfStr
, range
, kCFStringEncodingUTF8
, 0, FALSE
, (UInt8
*)rtn
->Data
, strLen
, &strLen
)) {
148 /* not convertible from native Unicode to UTF8 */
155 * Enum to string mappper.
159 * Each type of attribute has a name/value pair in a table of these:
166 /* declare one entry in a table of p12NameValuePair */
167 #define NVP(attr) {attr, #attr}
169 /* the NULL entry which terminates all p12NameValuePair tables */
170 #define NVP_END {0, NULL}
172 static const p12NameValuePair p7CITypeNames
[] =
178 NVP(CT_SignedEnvData
),
180 NVP(CT_EncryptedData
),
184 static const p12NameValuePair p12BagTypeNames
[] =
188 NVP(BT_ShroudedKeyBag
),
192 NVP(BT_SafeContentsBag
),
196 static const char *typeToStr(
198 const p12NameValuePair
*table
)
201 if(table
->value
== type
) {
209 const char *p12BagTypeStr(
210 NSS_P12_SB_Type type
)
212 return typeToStr(type
, p12BagTypeNames
);
215 const char *p7ContentInfoTypeStr(
218 return typeToStr(type
, p7CITypeNames
);
222 * OIDS for P12 and PKCS5 v1.5 (PBES1) encrypt and decrypt map to the following
227 CSSM_ALGORITHMS keyAlg
; // e.g., CSSM_ALGID_DES
228 CSSM_ALGORITHMS encrAlg
; // e.g., CSSM_ALGID_3DES_3KEY_EDE
229 CSSM_ALGORITHMS pbeHashAlg
; // SHA1 or MD5
230 uint32 keySizeInBits
;
231 uint32 blockSizeInBytes
; // for IV, optional
232 CSSM_PADDING padding
; // CSSM_PADDING_PKCS7, etc.
233 CSSM_ENCRYPT_MODE mode
; // CSSM_ALGMODE_CBCPadIV8, etc.
234 PKCS_Which pkcs
; // PW_PKCS12 (for this module) or PW_PKCS5_v1_5
237 static const PKCSOidInfo pkcsOidInfos
[] = {
238 /* PKCS12 first, the ones this module uses */
240 &CSSMOID_PKCS12_pbeWithSHAAnd128BitRC4
,
245 0, // RC4 is a stream cipher
251 &CSSMOID_PKCS12_pbeWithSHAAnd40BitRC4
,
256 0, // RC4 is a stream cipher
262 &CSSMOID_PKCS12_pbeWithSHAAnd3Key3DESCBC
,
263 CSSM_ALGID_3DES_3KEY
,
264 CSSM_ALGID_3DES_3KEY_EDE
,
269 CSSM_ALGMODE_CBCPadIV8
,
273 &CSSMOID_PKCS12_pbeWithSHAAnd2Key3DESCBC
,
274 CSSM_ALGID_3DES_2KEY
,
275 CSSM_ALGID_3DES_2KEY_EDE
,
280 CSSM_ALGMODE_CBCPadIV8
,
284 &CSSMOID_PKCS12_pbeWithSHAAnd128BitRC2CBC
,
291 CSSM_ALGMODE_CBCPadIV8
,
295 &CSSMOID_PKCS12_pbewithSHAAnd40BitRC2CBC
,
302 CSSM_ALGMODE_CBCPadIV8
,
306 /* PKCS5 v1.5, used for SecImportExport module */
308 &CSSMOID_PKCS5_pbeWithMD2AndDES
,
315 CSSM_ALGMODE_CBCPadIV8
,
319 &CSSMOID_PKCS5_pbeWithMD2AndRC2
,
326 CSSM_ALGMODE_CBCPadIV8
,
330 &CSSMOID_PKCS5_pbeWithMD5AndDES
,
337 CSSM_ALGMODE_CBCPadIV8
,
341 &CSSMOID_PKCS5_pbeWithMD5AndRC2
,
348 CSSM_ALGMODE_CBCPadIV8
,
352 &CSSMOID_PKCS5_pbeWithSHA1AndDES
,
359 CSSM_ALGMODE_CBCPadIV8
,
363 &CSSMOID_PKCS5_pbeWithSHA1AndRC2
,
370 CSSM_ALGMODE_CBCPadIV8
,
374 /* finally one for PKCS5 v2.0, which has its own means of
375 * cooking up all the parameters */
377 &CSSMOID_PKCS5_PBES2
,
386 #define NUM_PKCS_OID_INFOS (sizeof(pkcsOidInfos) / sizeof(pkcsOidInfos[1]))
388 /* map an OID to the components */
389 /* returns false if OID not found */
392 * NOTE: as of March 8 2004 this is also used by the SecImportExport
393 * module...not just PKCS12!
395 bool pkcsOidToParams(
397 CSSM_ALGORITHMS
&keyAlg
, // e.g., CSSM_ALGID_DES
398 CSSM_ALGORITHMS
&encrAlg
, // e.g., CSSM_ALGID_3DES_3KEY_EDE
399 CSSM_ALGORITHMS
&pbeHashAlg
, // SHA1 or MD5
400 uint32
&keySizeInBits
,
401 uint32
&blockSizeInBytes
, // for IV, optional
402 CSSM_PADDING
&padding
, // CSSM_PADDING_PKCS7, etc.
403 CSSM_ENCRYPT_MODE
&mode
, // CSSM_ALGMODE_CBCPadIV8, etc.
404 PKCS_Which
&pkcs
) // PW_PKCS5_v1_5 or PW_PKCS12
406 const PKCSOidInfo
*info
= pkcsOidInfos
;
409 for(unsigned dex
=0; dex
<NUM_PKCS_OID_INFOS
; dex
++) {
410 if(nssCompareCssmData(oid
, info
->oid
)) {
411 keyAlg
= info
->keyAlg
;
412 encrAlg
= info
->encrAlg
;
413 pbeHashAlg
= info
->pbeHashAlg
;
414 keySizeInBits
= info
->keySizeInBits
;
415 blockSizeInBytes
= info
->blockSizeInBytes
;
416 padding
= info
->padding
;
427 * Verify MAC on an existing PFX.
429 CSSM_RETURN
p12VerifyMac(
430 const NSS_P12_DecodedPFX
&pfx
,
431 CSSM_CSP_HANDLE cspHand
,
432 const CSSM_DATA
*pwd
, // unicode, double null terminated
433 const CSSM_KEY
*passKey
,
434 SecNssCoder
&coder
) // for temp mallocs
436 if(pfx
.macData
== NULL
) {
437 return CSSMERR_CSP_INVALID_SIGNATURE
;
439 NSS_P12_MacData
&macData
= *pfx
.macData
;
440 NSS_P7_DigestInfo
&digestInfo
= macData
.mac
;
441 CSSM_OID
&algOid
= digestInfo
.digestAlgorithm
.algorithm
;
442 CSSM_ALGORITHMS macAlg
;
443 if(!cssmOidToAlg(&algOid
, &macAlg
)) {
444 return CSSMERR_CSP_INVALID_ALGORITHM
;
446 uint32 iterCount
= 0;
447 CSSM_DATA
&citer
= macData
.iterations
;
448 if(!p12DataToInt(citer
, iterCount
)) {
449 return CSSMERR_CSP_INVALID_ATTR_ROUNDS
;
452 /* optional, default 1 */
457 * In classic fashion, the PKCS12 spec now says:
459 * When password integrity mode is used to secure a PFX PDU,
460 * an SHA-1 HMAC is computed on the BER-encoding of the contents
461 * of the content field of the authSafe field in the PFX PDU.
466 CSSM_RETURN crtn
= p12GenMac(cspHand
, *pfx
.authSafe
.content
.data
,
467 macAlg
, iterCount
, macData
.macSalt
, pwd
, passKey
, coder
, genMac
);
471 if(nssCompareCssmData(&genMac
, &digestInfo
.digest
)) {
475 return CSSMERR_CSP_VERIFY_FAILED
;
479 /* we generate 8 random bytes of salt */
480 #define P12_SALT_LEN 8
486 DevRandomGenerator rng
;
487 coder
.allocItem(salt
, P12_SALT_LEN
);
488 rng
.random(salt
.Data
, P12_SALT_LEN
);
492 * Generate random label string to allow associating an imported private
499 /* first a random uint32 */
501 DevRandomGenerator rng
;
503 CSSM_DATA cd
= {4, d
};
507 /* sprintf that into a real string */
508 coder
.allocItem(label
, 9);
509 memset(label
.Data
, 0, 9);
510 sprintf((char *)label
.Data
, "%08X", (unsigned)i
);
513 /* NULL algorithm parameters */
515 static const uint8 nullAlg
[2] = {SEC_ASN1_NULL
, 0};
517 void p12NullAlgParams(
518 CSSM_X509_ALGORITHM_IDENTIFIER
&algId
)
520 CSSM_DATA
&p
= algId
.parameters
;
521 p
.Data
= (uint8
*)nullAlg
;
526 * Free memory via specified plugin's app-level allocator
532 CSSM_API_MEMORY_FUNCS memFuncs
;
533 CSSM_RETURN crtn
= CSSM_GetAPIMemoryFunctions(hand
, &memFuncs
);
535 p12LogCssmError("CSSM_GetAPIMemoryFunctions", crtn
);
536 /* oh well, leak and continue */
539 memFuncs
.free_func(p
, memFuncs
.AllocRef
);
543 * Find private key by label, modify its Label attr to be the
544 * hash of the associated public key.
545 * Also optionally re-sets the key's PrintName attribute; used to reset
546 * this attr from the random label we create when first unwrap it
547 * to the friendly name we find later after parsing attributes.
548 * Detection of a duplicate key when updating the key's attributes
549 * results in a lookup of the original key and returning it in
552 CSSM_RETURN
p12SetPubKeyHash(
553 CSSM_CSP_HANDLE cspHand
, // where the key lives
554 CSSM_DL_DB_HANDLE dlDbHand
, // ditto
555 CSSM_DATA
&keyLabel
, // for DB lookup
556 CSSM_DATA_PTR newPrintName
, // optional
557 SecNssCoder
&coder
, // for mallocing newLabel
558 CSSM_DATA
&newLabel
, // RETURNED with label as hash
559 CSSM_KEY_PTR
&foundKey
) // RETURNED
562 CSSM_SELECTION_PREDICATE predicate
;
563 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
565 CSSM_HANDLE resultHand
= 0;
566 CSSM_DATA keyData
= {0, NULL
};
567 CSSM_CC_HANDLE ccHand
= 0;
568 CSSM_KEY_PTR privKey
= NULL
;
569 CSSM_DATA_PTR keyDigest
= NULL
;
571 assert(cspHand
!= 0);
572 query
.RecordType
= CSSM_DL_DB_RECORD_PRIVATE_KEY
;
573 query
.Conjunctive
= CSSM_DB_NONE
;
574 query
.NumSelectionPredicates
= 1;
575 predicate
.DbOperator
= CSSM_DB_EQUAL
;
577 predicate
.Attribute
.Info
.AttributeNameFormat
=
578 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
579 predicate
.Attribute
.Info
.Label
.AttributeName
=
580 (char*) P12_KEY_ATTR_LABEL_AND_HASH
;
581 predicate
.Attribute
.Info
.AttributeFormat
=
582 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
;
583 /* hope this cast is OK */
584 predicate
.Attribute
.Value
= &keyLabel
;
585 query
.SelectionPredicate
= &predicate
;
587 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
588 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
589 query
.QueryFlags
= CSSM_QUERY_RETURN_DATA
;
591 /* build Record attribute with one or two attrs */
592 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
593 CSSM_DB_ATTRIBUTE_DATA attr
[2];
594 attr
[0].Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
595 attr
[0].Info
.Label
.AttributeName
= (char*) P12_KEY_ATTR_LABEL_AND_HASH
;
596 attr
[0].Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_BLOB
;
598 attr
[1].Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
599 attr
[1].Info
.Label
.AttributeName
= (char*) P12_KEY_ATTR_PRINT_NAME
;
600 attr
[1].Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_BLOB
;
602 recordAttrs
.DataRecordType
= CSSM_DL_DB_RECORD_PRIVATE_KEY
;
603 recordAttrs
.NumberOfAttributes
= newPrintName
? 2 : 1;
604 recordAttrs
.AttributeData
= attr
;
606 crtn
= CSSM_DL_DataGetFirst(dlDbHand
,
612 /* abort only on success */
613 if(crtn
!= CSSM_OK
) {
614 p12LogCssmError("CSSM_DL_DataGetFirst", crtn
);
615 p12ErrorLog("***p12SetPubKeyHash: can't find private key\n");
618 /* subsequent errors to errOut: */
619 if(keyData
.Data
== NULL
) {
620 p12ErrorLog("***p12SetPubKeyHash: private key lookup failure\n");
621 crtn
= CSSMERR_CSSM_INTERNAL_ERROR
;
624 privKey
= (CSSM_KEY_PTR
)keyData
.Data
;
626 /* public key hash via passthrough - works on any key, any CSP/CSPDL.... */
628 * Warning! This relies on the current default ACL meaning "allow this
629 * current app to access this private key" since we created the key.
631 crtn
= CSSM_CSP_CreatePassThroughContext(cspHand
, privKey
, &ccHand
);
633 p12LogCssmError("CSSM_CSP_CreatePassThroughContext", crtn
);
636 crtn
= CSSM_CSP_PassThrough(ccHand
,
637 CSSM_APPLECSP_KEYDIGEST
,
639 (void **)&keyDigest
);
641 p12LogCssmError("CSSM_CSP_PassThrough", crtn
);
646 * Replace Label attr data with hash.
647 * NOTE: the module which allocated this attribute data - a DL -
648 * was loaded and attached by out client layer, not by us. Thus
649 * we can't use the memory allocator functions *we* used when
650 * attaching to the CSP - we have to use the ones
651 * which the client registered with the DL.
653 freeCssmMemory(dlDbHand
.DLHandle
, attr
[0].Value
->Data
);
654 freeCssmMemory(dlDbHand
.DLHandle
, attr
[0].Value
);
656 freeCssmMemory(dlDbHand
.DLHandle
, attr
[1].Value
->Data
);
657 freeCssmMemory(dlDbHand
.DLHandle
, attr
[1].Value
);
659 /* modify key attributes */
660 attr
[0].Value
= keyDigest
;
662 attr
[1].Value
= newPrintName
;
664 crtn
= CSSM_DL_DataModify(dlDbHand
,
665 CSSM_DL_DB_RECORD_PRIVATE_KEY
,
668 NULL
, // DataToBeModified
669 CSSM_DB_MODIFY_ATTRIBUTE_REPLACE
);
672 /* give caller the key's new label */
673 coder
.allocCopyItem(*keyDigest
, newLabel
);
676 p12LogCssmError("CSSM_DL_DataModify", crtn
);
678 case CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA
:
681 * Special case: dup private key. The label we just tried to modify is
682 * the public key hash so we can be confident that this really is a dup.
683 * Delete it, look up the original, and return the original to caller.
685 CSSM_RETURN drtn
= CSSM_DL_DataDelete(dlDbHand
, record
);
687 p12LogCssmError("CSSM_DL_DataDelete on dup key", drtn
);
692 /* Free items created in last search */
693 CSSM_DL_DataAbortQuery(dlDbHand
, resultHand
);
695 CSSM_DL_FreeUniqueRecord(dlDbHand
, record
);
698 /* lookup by label as public key hash this time */
699 predicate
.Attribute
.Value
= keyDigest
;
700 drtn
= CSSM_DL_DataGetFirst(dlDbHand
,
703 NULL
, // no attrs this time
707 p12LogCssmError("CSSM_DL_DataGetFirst on original key", crtn
);
711 foundKey
= (CSSM_KEY_PTR
)keyData
.Data
;
712 /* give caller the key's actual label */
713 coder
.allocCopyItem(*keyDigest
, newLabel
);
721 CSSM_DL_DataAbortQuery(dlDbHand
, resultHand
);
724 CSSM_DL_FreeUniqueRecord(dlDbHand
, record
);
727 CSSM_DeleteContext(ccHand
);
730 /* key created by the CSPDL */
731 CSSM_FreeKey(cspHand
, NULL
, privKey
, CSSM_FALSE
);
732 freeCssmMemory(dlDbHand
.DLHandle
, privKey
);
735 /* mallocd by someone else's CSP */
736 freeCssmMemory(cspHand
, keyDigest
->Data
);
737 freeCssmMemory(cspHand
, keyDigest
);
743 * Given a context specified via a CSSM_CC_HANDLE, add a new
744 * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType,
745 * AttributeLength, and an untyped pointer.
747 CSSM_RETURN
p12AddContextAttribute(CSSM_CC_HANDLE CCHandle
,
748 uint32 AttributeType
,
749 uint32 AttributeLength
,
750 const void *AttributePtr
)
752 CSSM_CONTEXT_ATTRIBUTE newAttr
;
755 newAttr
.AttributeType
= AttributeType
;
756 newAttr
.AttributeLength
= AttributeLength
;
757 newAttr
.Attribute
.Data
= (CSSM_DATA_PTR
)AttributePtr
;
758 crtn
= CSSM_UpdateContextAttributes(CCHandle
, 1, &newAttr
);
760 p12LogCssmError("CSSM_UpdateContextAttributes", crtn
);
766 * Find private key by specified label, delete it.
768 CSSM_RETURN
p12DeleteKey(
769 CSSM_DL_DB_HANDLE dlDbHand
,
770 const CSSM_DATA
&keyLabel
)
773 CSSM_SELECTION_PREDICATE predicate
;
774 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
776 CSSM_HANDLE resultHand
= 0;
778 query
.RecordType
= CSSM_DL_DB_RECORD_PRIVATE_KEY
;
779 query
.Conjunctive
= CSSM_DB_NONE
;
780 query
.NumSelectionPredicates
= 1;
781 predicate
.DbOperator
= CSSM_DB_EQUAL
;
783 predicate
.Attribute
.Info
.AttributeNameFormat
=
784 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
785 predicate
.Attribute
.Info
.Label
.AttributeName
=
786 (char*) P12_KEY_ATTR_LABEL_AND_HASH
;
787 predicate
.Attribute
.Info
.AttributeFormat
=
788 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
;
789 predicate
.Attribute
.Value
= const_cast<CSSM_DATA_PTR
>(&keyLabel
);
791 query
.SelectionPredicate
= &predicate
;
792 query
.QueryLimits
.TimeLimit
= 0;
793 query
.QueryLimits
.SizeLimit
= 1;
794 query
.QueryFlags
= 0;
796 crtn
= CSSM_DL_DataGetFirst(dlDbHand
,
799 NULL
, // attrs - don't need 'em
800 NULL
, // theData - don't need it
802 /* abort only on success */
804 p12LogCssmError("CSSM_DL_DataGetFirst", crtn
);
805 p12ErrorLog("***p12DeleteKey: can't find private key\n");
809 crtn
= CSSM_DL_DataDelete(dlDbHand
, record
);
811 p12LogCssmError("CSSM_DL_DataDelete", crtn
);
812 p12ErrorLog("***p12DeleteKey: can't delete private key\n");
815 CSSM_DL_DataAbortQuery(dlDbHand
, resultHand
);
816 CSSM_DL_FreeUniqueRecord(dlDbHand
, record
);
820 /* convert App passphrase to array of chars used in P12 PBE */
821 void p12ImportPassPhrase(
822 CFStringRef inPhrase
,
824 CSSM_DATA
&outPhrase
)
826 CFDataRef cfData
= CFStringCreateExternalRepresentation(NULL
,
827 inPhrase
, kCFStringEncodingUTF8
, 0);
829 p12ErrorLog("***p12ImportPassPhrase: can't convert passphrase to UTF8\n");
830 MacOSError::throwMe(errSecParam
);
832 CFIndex keyLen
= CFDataGetLength(cfData
);
833 coder
.allocItem(outPhrase
, keyLen
);
834 memmove(outPhrase
.Data
, CFDataGetBytePtr(cfData
), keyLen
);