1/*
2 * The contents of this file are subject to the Mozilla Public
3 * License Version 1.1 (the "License"); you may not use this file
4 * except in compliance with the License. You may obtain a copy of
5 * the License at http://www.mozilla.org/MPL/
6 *
7 * Software distributed under the License is distributed on an "AS
8 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9 * implied. See the License for the specific language governing
10 * rights and limitations under the License.
11 *
12 * The Original Code is the Netscape security libraries.
13 *
14 * The Initial Developer of the Original Code is Netscape
15 * Communications Corporation.  Portions created by Netscape are
16 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
17 * Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * Alternatively, the contents of this file may be used under the
22 * terms of the GNU General Public License Version 2 or later (the
23 * "GPL"), in which case the provisions of the GPL are applicable
24 * instead of those above.  If you wish to allow use of your
25 * version of this file only under the terms of the GPL and not to
26 * allow others to use your version of this file under the MPL,
27 * indicate your decision by deleting the provisions above and
28 * replace them with the notice and other provisions required by
29 * the GPL.  If you do not delete the provisions above, a recipient
30 * may use your version of this file under either the MPL or the
31 * GPL.
32 */
33
34/*
35 * CMS public key crypto
36 */
37
38#include "cmslocal.h"
39
40#include "SecAsn1Item.h"
41#include "secoid.h"
42#include "cryptohi.h"
43
44#include <security_asn1/secasn1.h>
45#include <security_asn1/secerr.h>
46#include <security_asn1/secport.h>
47
48#include <Security/SecCertificateInternal.h>
49#include <Security/SecKeyPriv.h>
50
51/* ====== RSA ======================================================================= */
52
53/*
54 * SecCmsUtilEncryptSymKeyRSA - wrap a symmetric key with RSA
55 *
56 * this function takes a symmetric key and encrypts it using an RSA public key
57 * according to PKCS#1 and RFC2633 (S/MIME)
58 */
59OSStatus
60SecCmsUtilEncryptSymKeyRSA(PLArenaPool *poolp, SecCertificateRef cert,
61                              SecSymmetricKeyRef bulkkey,
62                              SecAsn1Item * encKey)
63{
64    OSStatus rv;
65    SecPublicKeyRef publickey;
66#if USE_CDSA_CRYPTO
67    rv = SecCertificateCopyPublicKey(cert,&publickey);
68#else
69    publickey = SecCertificateCopyPublicKey(cert);
70#endif
71    if (publickey == NULL)
72	return SECFailure;
73
74    rv = SecCmsUtilEncryptSymKeyRSAPubKey(poolp, publickey, bulkkey, encKey);
75    CFRelease(publickey);
76    return rv;
77}
78
79OSStatus
80SecCmsUtilEncryptSymKeyRSAPubKey(PLArenaPool *poolp,
81				 SecPublicKeyRef publickey,
82				 SecSymmetricKeyRef bulkkey, SecAsn1Item * encKey)
83{
84    OSStatus rv;
85    size_t data_len;
86    //KeyType keyType;
87    void *mark = NULL;
88
89    mark = PORT_ArenaMark(poolp);
90    if (!mark)
91	goto loser;
92
93#if 0
94    /* sanity check */
95    keyType = SECKEY_GetPublicKeyType(publickey);
96    PORT_Assert(keyType == rsaKey);
97    if (keyType != rsaKey) {
98	goto loser;
99    }
100#endif
101    /* allocate memory for the encrypted key */
102#if USE_CDSA_CRYPTO
103    rv = SecKeyGetStrengthInBits(publickey, NULL, &data_len);
104    if (rv)
105	goto loser;
106    // Convert length to bytes;
107    data_len >>= 2;
108#else
109    data_len = SecKeyGetSize(publickey, kSecKeyEncryptedDataSize);
110#endif
111
112    encKey->Data = (unsigned char*)PORT_ArenaAlloc(poolp, data_len);
113    encKey->Length = data_len;
114    if (encKey->Data == NULL)
115	goto loser;
116
117    /* encrypt the key now */
118    rv = WRAP_PubWrapSymKey(publickey, bulkkey, encKey);
119    if (rv != SECSuccess)
120	goto loser;
121
122    PORT_ArenaUnmark(poolp, mark);
123    return SECSuccess;
124
125loser:
126    if (mark) {
127	PORT_ArenaRelease(poolp, mark);
128    }
129    return SECFailure;
130}
131
132/*
133 * SecCmsUtilDecryptSymKeyRSA - unwrap a RSA-wrapped symmetric key
134 *
135 * this function takes an RSA-wrapped symmetric key and unwraps it, returning a symmetric
136 * key handle. Please note that the actual unwrapped key data may not be allowed to leave
137 * a hardware token...
138 */
139SecSymmetricKeyRef
140SecCmsUtilDecryptSymKeyRSA(SecPrivateKeyRef privkey, SecAsn1Item * encKey, SECOidTag bulkalgtag)
141{
142    /* that's easy */
143    return WRAP_PubUnwrapSymKey(privkey, encKey, bulkalgtag);
144}
145
146#if 0
147// @@@ Implement Fortezza and Diffie hellman support
148
149/* ====== MISSI (Fortezza) ========================================================== */
150
151extern const SecAsn1Template NSS_SMIMEKEAParamTemplateAllParams[];
152
153OSStatus
154SecCmsUtilEncryptSymKeyMISSI(PLArenaPool *poolp, SecCertificateRef cert, SecSymmetricKeyRef bulkkey,
155			SECOidTag symalgtag, SecAsn1Item * encKey, SecAsn1Item * *pparams, void *pwfn_arg)
156{
157    SECOidTag certalgtag;	/* the certificate's encryption algorithm */
158    SECOidTag encalgtag;	/* the algorithm used for key exchange/agreement */
159    OSStatus rv = SECFailure;
160    SecAsn1Item * params = NULL;
161    OSStatus err;
162    SecSymmetricKeyRef tek;
163    SecCertificateRef ourCert;
164    SecPublicKeyRef ourPubKey, *publickey = NULL;
165    SecPrivateKeyRef ourPrivKey = NULL;
166    SecCmsKEATemplateSelector whichKEA = SecCmsKEAInvalid;
167    SecCmsSMIMEKEAParameters keaParams;
168    PLArenaPool *arena = NULL;
169    const SECAlgorithmID *algid;
170
171    /* Clear keaParams, since cleanup code checks the lengths */
172    (void) memset(&keaParams, 0, sizeof(keaParams));
173
174#if USE_CDSA_CRYPTO
175    SecCertificateGetAlgorithmID(cert,&algid);
176#endif
177
178    certalgtag = SECOID_GetAlgorithmTag(algid);
179    PORT_Assert(certalgtag == SEC_OID_MISSI_KEA_DSS_OLD ||
180		certalgtag == SEC_OID_MISSI_KEA_DSS ||
181		certalgtag == SEC_OID_MISSI_KEA);
182
183#define SMIME_FORTEZZA_RA_LENGTH 128
184#define SMIME_FORTEZZA_IV_LENGTH 24
185#define SMIME_FORTEZZA_MAX_KEY_SIZE 256
186
187    /* We really want to show our KEA tag as the key exchange algorithm tag. */
188    encalgtag = SEC_OID_NETSCAPE_SMIME_KEA;
189
190    /* Get the public key of the recipient. */
191    publickey = CERT_ExtractPublicKey(cert);
192    if (publickey == NULL) goto loser;
193
194    /* Find our own cert, and extract its keys. */
195    ourCert = PK11_FindBestKEAMatch(cert, pwfn_arg);
196    if (ourCert == NULL) goto loser;
197
198    arena = PORT_NewArena(1024);
199    if (arena == NULL)
200	goto loser;
201
202    ourPubKey = CERT_ExtractPublicKey(ourCert);
203    if (ourPubKey == NULL) {
204	CERT_DestroyCertificate(ourCert);
205	goto loser;
206    }
207
208    /* While we're here, copy the public key into the outgoing
209     * KEA parameters. */
210    SECITEM_CopyItem(arena, &(keaParams.originatorKEAKey), &(ourPubKey->u.fortezza.KEAKey));
211    SECKEY_DestroyPublicKey(ourPubKey);
212    ourPubKey = NULL;
213
214    /* Extract our private key in order to derive the KEA key. */
215    ourPrivKey = PK11_FindKeyByAnyCert(ourCert, pwfn_arg);
216    CERT_DestroyCertificate(ourCert); /* we're done with this */
217    if (!ourPrivKey)
218	goto loser;
219
220    /* Prepare raItem with 128 bytes (filled with zeros). */
221    keaParams.originatorRA.Data = (unsigned char *)PORT_ArenaAlloc(arena,SMIME_FORTEZZA_RA_LENGTH);
222    keaParams.originatorRA.Length = SMIME_FORTEZZA_RA_LENGTH;
223
224    /* Generate the TEK (token exchange key) which we use
225     * to wrap the bulk encryption key. (keaparams.originatorRA) will be
226     * filled with a random seed which we need to send to
227     * the recipient. (user keying material in RFC2630/DSA speak) */
228    tek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE,
229			 &keaParams.originatorRA, NULL,
230			 CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP,
231			 CKA_WRAP, 0,  pwfn_arg);
232
233    SECKEY_DestroyPublicKey(publickey);
234    SECKEY_DestroyPrivateKey(ourPrivKey);
235    publickey = NULL;
236    ourPrivKey = NULL;
237
238    if (!tek)
239	goto loser;
240
241    /* allocate space for the wrapped key data */
242    encKey->Data = (unsigned char *)PORT_ArenaAlloc(poolp, SMIME_FORTEZZA_MAX_KEY_SIZE);
243    encKey->Length = SMIME_FORTEZZA_MAX_KEY_SIZE;
244
245    if (encKey->Data == NULL) {
246	CFRelease(tek);
247	goto loser;
248    }
249
250    /* Wrap the bulk key. What we do with the resulting data
251       depends on whether we're using Skipjack to wrap the key. */
252    switch (PK11_AlgtagToMechanism(symalgtag)) {
253    case CKM_SKIPJACK_CBC64:
254    case CKM_SKIPJACK_ECB64:
255    case CKM_SKIPJACK_OFB64:
256    case CKM_SKIPJACK_CFB64:
257    case CKM_SKIPJACK_CFB32:
258    case CKM_SKIPJACK_CFB16:
259    case CKM_SKIPJACK_CFB8:
260	/* SKIPJACK, we use the wrap mechanism because we can do it on the hardware */
261	err = PK11_WrapSymKey(CKM_SKIPJACK_WRAP, NULL, tek, bulkkey, encKey);
262	whichKEA = SecCmsKEAUsesSkipjack;
263	break;
264    default:
265	/* Not SKIPJACK, we encrypt the raw key data */
266	keaParams.nonSkipjackIV.Data =
267	  (unsigned char *)PORT_ArenaAlloc(arena, SMIME_FORTEZZA_IV_LENGTH);
268	keaParams.nonSkipjackIV.Length = SMIME_FORTEZZA_IV_LENGTH;
269	err = PK11_WrapSymKey(CKM_SKIPJACK_CBC64, &keaParams.nonSkipjackIV, tek, bulkkey, encKey);
270	if (err != SECSuccess)
271	    goto loser;
272
273	if (encKey->Length != PK11_GetKeyLength(bulkkey)) {
274	    /* The size of the encrypted key is not the same as
275	       that of the original bulk key, presumably due to
276	       padding. Encode and store the real size of the
277	       bulk key. */
278	    if (SEC_ASN1EncodeInteger(arena, &keaParams.bulkKeySize, PK11_GetKeyLength(bulkkey)) == NULL)
279		err = (OSStatus)PORT_GetError();
280	    else
281		/* use full template for encoding */
282		whichKEA = SecCmsKEAUsesNonSkipjackWithPaddedEncKey;
283	}
284	else
285	    /* enc key length == bulk key length */
286	    whichKEA = SecCmsKEAUsesNonSkipjack;
287	break;
288    }
289
290    CFRelease(tek);
291
292    if (err != SECSuccess)
293	goto loser;
294
295    PORT_Assert(whichKEA != SecCmsKEAInvalid);
296
297    /* Encode the KEA parameters into the recipient info. */
298    params = SEC_ASN1EncodeItem(poolp, NULL, &keaParams, nss_cms_get_kea_template(whichKEA));
299    if (params == NULL)
300	goto loser;
301
302    /* pass back the algorithm params */
303    *pparams = params;
304
305    rv = SECSuccess;
306
307loser:
308    if (arena)
309	PORT_FreeArena(arena, PR_FALSE);
310    if (publickey)
311        SECKEY_DestroyPublicKey(publickey);
312    if (ourPrivKey)
313        SECKEY_DestroyPrivateKey(ourPrivKey);
314    return rv;
315}
316
317SecSymmetricKeyRef
318SecCmsUtilDecryptSymKeyMISSI(SecPrivateKeyRef privkey, SecAsn1Item * encKey, SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg)
319{
320    /* fortezza: do a key exchange */
321    OSStatus err;
322    CK_MECHANISM_TYPE bulkType;
323    SecSymmetricKeyRef tek;
324    SecPublicKeyRef originatorPubKey;
325    SecCmsSMIMEKEAParameters keaParams;
326    SecSymmetricKeyRef bulkkey;
327    int bulkLength;
328
329    (void) memset(&keaParams, 0, sizeof(keaParams));
330
331    /* NOTE: this uses the SMIME v2 recipientinfo for compatibility.
332       All additional KEA parameters are DER-encoded in the encryption algorithm parameters */
333
334    /* Decode the KEA algorithm parameters. */
335    err = SEC_ASN1DecodeItem(NULL, &keaParams, NSS_SMIMEKEAParamTemplateAllParams,
336			     &(keyEncAlg->parameters));
337    if (err != SECSuccess)
338	goto loser;
339
340    /* get originator's public key */
341   originatorPubKey = PK11_MakeKEAPubKey(keaParams.originatorKEAKey.Data,
342			   keaParams.originatorKEAKey.Length);
343   if (originatorPubKey == NULL)
344	  goto loser;
345
346   /* Generate the TEK (token exchange key) which we use to unwrap the bulk encryption key.
347      The Derive function generates a shared secret and combines it with the originatorRA
348      data to come up with an unique session key */
349   tek = PK11_PubDerive(privkey, originatorPubKey, PR_FALSE,
350			 &keaParams.originatorRA, NULL,
351			 CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP,
352			 CKA_WRAP, 0, pwfn_arg);
353   SECKEY_DestroyPublicKey(originatorPubKey);	/* not needed anymore */
354   if (tek == NULL)
355	goto loser;
356
357    /* Now that we have the TEK, unwrap the bulk key
358       with which to decrypt the message. We have to
359       do one of two different things depending on
360       whether Skipjack was used for *bulk* encryption
361       of the message. */
362    bulkType = PK11_AlgtagToMechanism(bulkalgtag);
363    switch (bulkType) {
364    case CKM_SKIPJACK_CBC64:
365    case CKM_SKIPJACK_ECB64:
366    case CKM_SKIPJACK_OFB64:
367    case CKM_SKIPJACK_CFB64:
368    case CKM_SKIPJACK_CFB32:
369    case CKM_SKIPJACK_CFB16:
370    case CKM_SKIPJACK_CFB8:
371	/* Skipjack is being used as the bulk encryption algorithm.*/
372	/* Unwrap the bulk key. */
373	bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP, NULL,
374				    encKey, CKM_SKIPJACK_CBC64, CKA_DECRYPT, 0);
375	break;
376    default:
377	/* Skipjack was not used for bulk encryption of this
378	   message. Use Skipjack CBC64, with the nonSkipjackIV
379	   part of the KEA key parameters, to decrypt
380	   the bulk key. If the optional parameter bulkKeySize is present,
381	   bulk key size is different than the encrypted key size */
382	if (keaParams.bulkKeySize.Length > 0) {
383	    err = SEC_ASN1DecodeItem(NULL, &bulkLength,
384				     SEC_ASN1_GET(SEC_IntegerTemplate),
385				     &keaParams.bulkKeySize);
386	    if (err != SECSuccess)
387		goto loser;
388	}
389
390	bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_CBC64, &keaParams.nonSkipjackIV,
391				    encKey, bulkType, CKA_DECRYPT, bulkLength);
392	break;
393    }
394    return bulkkey;
395loser:
396    return NULL;
397}
398
399/* ====== ESDH (Ephemeral-Static Diffie-Hellman) ==================================== */
400
401OSStatus
402SecCmsUtilEncryptSymKeyESDH(PLArenaPool *poolp, SecCertificateRef cert, SecSymmetricKeyRef key,
403			SecAsn1Item * encKey, SecAsn1Item * *ukm, SECAlgorithmID *keyEncAlg,
404			SecAsn1Item * pubKey)
405{
406#if 0 /* not yet done */
407    SECOidTag certalgtag;	/* the certificate's encryption algorithm */
408    SECOidTag encalgtag;	/* the algorithm used for key exchange/agreement */
409    OSStatus rv;
410    SecAsn1Item * params = NULL;
411    int data_len;
412    OSStatus err;
413    SecSymmetricKeyRef tek;
414    SecCertificateRef ourCert;
415    SecPublicKeyRef ourPubKey;
416    SecCmsKEATemplateSelector whichKEA = SecCmsKEAInvalid;
417
418    certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
419    PORT_Assert(certalgtag == SEC_OID_X942_DIFFIE_HELMAN_KEY);
420
421    /* We really want to show our KEA tag as the key exchange algorithm tag. */
422    encalgtag = SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN;
423
424    /* Get the public key of the recipient. */
425    publickey = CERT_ExtractPublicKey(cert);
426    if (publickey == NULL) goto loser;
427
428    /* XXXX generate a DH key pair on a PKCS11 module (XXX which parameters?) */
429    /* XXXX */ourCert = PK11_FindBestKEAMatch(cert, wincx);
430    if (ourCert == NULL) goto loser;
431
432    arena = PORT_NewArena(1024);
433    if (arena == NULL) goto loser;
434
435    /* While we're here, extract the key pair's public key data and copy it into */
436    /* the outgoing parameters. */
437    /* XXXX */ourPubKey = CERT_ExtractPublicKey(ourCert);
438    if (ourPubKey == NULL)
439    {
440	goto loser;
441    }
442    SECITEM_CopyItem(arena, pubKey, /* XXX */&(ourPubKey->u.fortezza.KEAKey));
443    SECKEY_DestroyPublicKey(ourPubKey); /* we only need the private key from now on */
444    ourPubKey = NULL;
445
446    /* Extract our private key in order to derive the KEA key. */
447    ourPrivKey = PK11_FindKeyByAnyCert(ourCert,wincx);
448    CERT_DestroyCertificate(ourCert); /* we're done with this */
449    if (!ourPrivKey) goto loser;
450
451    /* If ukm desired, prepare it - allocate enough space (filled with zeros). */
452    if (ukm) {
453	ukm->Data = (unsigned char*)PORT_ArenaZAlloc(arena,/* XXXX */);
454	ukm->Length = /* XXXX */;
455    }
456
457    /* Generate the KEK (key exchange key) according to RFC2631 which we use
458     * to wrap the bulk encryption key. */
459    kek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE,
460			 ukm, NULL,
461			 /* XXXX */CKM_KEA_KEY_DERIVE, /* XXXX */CKM_SKIPJACK_WRAP,
462			 CKA_WRAP, 0, wincx);
463
464    SECKEY_DestroyPublicKey(publickey);
465    SECKEY_DestroyPrivateKey(ourPrivKey);
466    publickey = NULL;
467    ourPrivKey = NULL;
468
469    if (!kek)
470	goto loser;
471
472    /* allocate space for the encrypted CEK (bulk key) */
473    encKey->Data = (unsigned char*)PORT_ArenaAlloc(poolp, SMIME_FORTEZZA_MAX_KEY_SIZE);
474    encKey->Length = SMIME_FORTEZZA_MAX_KEY_SIZE;
475
476    if (encKey->Data == NULL)
477    {
478	CFRelease(kek);
479	goto loser;
480    }
481
482
483    /* Wrap the bulk key using CMSRC2WRAP or CMS3DESWRAP, depending on the */
484    /* bulk encryption algorithm */
485    switch (/* XXXX */PK11_AlgtagToMechanism(enccinfo->encalg))
486    {
487    case /* XXXX */CKM_SKIPJACK_CFB8:
488	err = PK11_WrapSymKey(/* XXXX */CKM_CMS3DES_WRAP, NULL, kek, bulkkey, encKey);
489	whichKEA = SecCmsKEAUsesSkipjack;
490	break;
491    case /* XXXX */CKM_SKIPJACK_CFB8:
492	err = PK11_WrapSymKey(/* XXXX */CKM_CMSRC2_WRAP, NULL, kek, bulkkey, encKey);
493	whichKEA = SecCmsKEAUsesSkipjack;
494	break;
495    default:
496	/* XXXX what do we do here? Neither RC2 nor 3DES... */
497        err = SECFailure;
498        /* set error */
499	break;
500    }
501
502    CFRelease(kek);	/* we do not need the KEK anymore */
503    if (err != SECSuccess)
504	goto loser;
505
506    PORT_Assert(whichKEA != SecCmsKEAInvalid);
507
508    /* see RFC2630 12.3.1.1 "keyEncryptionAlgorithm must be ..." */
509    /* params is the DER encoded key wrap algorithm (with parameters!) (XXX) */
510    params = SEC_ASN1EncodeItem(arena, NULL, &keaParams, sec_pkcs7_get_kea_template(whichKEA));
511    if (params == NULL)
512	goto loser;
513
514    /* now set keyEncAlg */
515    rv = SECOID_SetAlgorithmID(poolp, keyEncAlg, SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN, params);
516    if (rv != SECSuccess)
517	goto loser;
518
519    /* XXXXXXX this is not right yet */
520loser:
521    if (arena) {
522	PORT_FreeArena(arena, PR_FALSE);
523    }
524    if (publickey) {
525        SECKEY_DestroyPublicKey(publickey);
526    }
527    if (ourPrivKey) {
528        SECKEY_DestroyPrivateKey(ourPrivKey);
529    }
530#endif
531    return SECFailure;
532}
533
534SecSymmetricKeyRef
535SecCmsUtilDecryptSymKeyESDH(SecPrivateKeyRef privkey, SecAsn1Item * encKey, SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg)
536{
537#if 0 /* not yet done */
538    OSStatus err;
539    CK_MECHANISM_TYPE bulkType;
540    SecSymmetricKeyRef tek;
541    SecPublicKeyRef originatorPubKey;
542    SecCmsSMIMEKEAParameters keaParams;
543
544   /* XXXX get originator's public key */
545   originatorPubKey = PK11_MakeKEAPubKey(keaParams.originatorKEAKey.Data,
546			   keaParams.originatorKEAKey.Length);
547   if (originatorPubKey == NULL)
548      goto loser;
549
550   /* Generate the TEK (token exchange key) which we use to unwrap the bulk encryption key.
551      The Derive function generates a shared secret and combines it with the originatorRA
552      data to come up with an unique session key */
553   tek = PK11_PubDerive(privkey, originatorPubKey, PR_FALSE,
554			 &keaParams.originatorRA, NULL,
555			 CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP,
556			 CKA_WRAP, 0, pwfn_arg);
557   SECKEY_DestroyPublicKey(originatorPubKey);	/* not needed anymore */
558   if (tek == NULL)
559	goto loser;
560
561    /* Now that we have the TEK, unwrap the bulk key
562       with which to decrypt the message. */
563    /* Skipjack is being used as the bulk encryption algorithm.*/
564    /* Unwrap the bulk key. */
565    bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP, NULL,
566				encKey, CKM_SKIPJACK_CBC64, CKA_DECRYPT, 0);
567
568    return bulkkey;
569
570loser:
571#endif
572    return NULL;
573}
574
575#endif
576