/src/nss-nspr/nss/lib/cryptohi/secsign.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Signature stuff. |
3 | | * |
4 | | * This Source Code Form is subject to the terms of the Mozilla Public |
5 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
6 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
7 | | |
8 | | #include <stdio.h> |
9 | | #include "cryptohi.h" |
10 | | #include "sechash.h" |
11 | | #include "secder.h" |
12 | | #include "keyhi.h" |
13 | | #include "secoid.h" |
14 | | #include "secdig.h" |
15 | | #include "pk11func.h" |
16 | | #include "secerr.h" |
17 | | #include "keyi.h" |
18 | | #include "nss.h" |
19 | | |
20 | | struct SGNContextStr { |
21 | | SECOidTag signalg; |
22 | | SECOidTag hashalg; |
23 | | CK_MECHANISM_TYPE mech; |
24 | | void *hashcx; |
25 | | /* if we are using explicitly hashing, this value will be non-null */ |
26 | | const SECHashObject *hashobj; |
27 | | /* if we are using the combined mechanism, this value will be non-null */ |
28 | | PK11Context *signcx; |
29 | | SECKEYPrivateKey *key; |
30 | | SECItem mechparams; |
31 | | }; |
32 | | |
33 | | static SGNContext * |
34 | | sgn_NewContext(SECOidTag alg, SECItem *params, SECKEYPrivateKey *key) |
35 | 0 | { |
36 | 0 | SGNContext *cx; |
37 | 0 | SECOidTag hashalg, signalg; |
38 | 0 | CK_MECHANISM_TYPE mech; |
39 | 0 | SECItem mechparams; |
40 | 0 | KeyType keyType; |
41 | 0 | PRUint32 policyFlags; |
42 | 0 | PRInt32 optFlags; |
43 | 0 | SECStatus rv; |
44 | | |
45 | | /* OK, map a PKCS #7 hash and encrypt algorithm into |
46 | | * a standard hashing algorithm. Why did we pass in the whole |
47 | | * PKCS #7 algTag if we were just going to change here you might |
48 | | * ask. Well the answer is for some cards we may have to do the |
49 | | * hashing on card. It may not support CKM_RSA_PKCS sign algorithm, |
50 | | * it may just support CKM_SHA1_RSA_PKCS and/or CKM_MD5_RSA_PKCS. |
51 | | */ |
52 | | /* we have a private key, not a public key, so don't pass it in */ |
53 | 0 | rv = sec_DecodeSigAlg(NULL, alg, params, &signalg, &hashalg, &mech, |
54 | 0 | &mechparams); |
55 | 0 | if (rv != SECSuccess) { |
56 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
57 | 0 | return NULL; |
58 | 0 | } |
59 | 0 | keyType = seckey_GetKeyType(signalg); |
60 | | |
61 | | /* verify our key type */ |
62 | 0 | if (key->keyType != keyType && |
63 | 0 | !((key->keyType == dsaKey) && (keyType == fortezzaKey)) && |
64 | 0 | !((key->keyType == rsaKey) && (keyType == rsaPssKey))) { |
65 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
66 | 0 | goto loser; |
67 | 0 | } |
68 | 0 | if (NSS_OptionGet(NSS_KEY_SIZE_POLICY_FLAGS, &optFlags) != SECFailure) { |
69 | 0 | if (optFlags & NSS_KEY_SIZE_POLICY_SIGN_FLAG) { |
70 | 0 | rv = SECKEY_EnforceKeySize(key->keyType, |
71 | 0 | SECKEY_PrivateKeyStrengthInBits(key), |
72 | 0 | SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); |
73 | 0 | if (rv != SECSuccess) { |
74 | 0 | goto loser; |
75 | 0 | } |
76 | 0 | } |
77 | 0 | } |
78 | | /* check the policy on the hash algorithm */ |
79 | 0 | if ((NSS_GetAlgorithmPolicy(hashalg, &policyFlags) == SECFailure) || |
80 | 0 | !(policyFlags & NSS_USE_ALG_IN_ANY_SIGNATURE)) { |
81 | 0 | PORT_SetError(SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); |
82 | 0 | goto loser; |
83 | 0 | } |
84 | | /* check the policy on the encryption algorithm */ |
85 | 0 | if ((NSS_GetAlgorithmPolicy(signalg, &policyFlags) == SECFailure) || |
86 | 0 | !(policyFlags & NSS_USE_ALG_IN_ANY_SIGNATURE)) { |
87 | 0 | PORT_SetError(SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); |
88 | 0 | goto loser; |
89 | 0 | } |
90 | | |
91 | 0 | cx = (SGNContext *)PORT_ZAlloc(sizeof(SGNContext)); |
92 | 0 | if (!cx) { |
93 | 0 | goto loser; |
94 | 0 | } |
95 | 0 | cx->hashalg = hashalg; |
96 | 0 | cx->signalg = signalg; |
97 | 0 | cx->mech = mech; |
98 | 0 | cx->key = key; |
99 | 0 | cx->mechparams = mechparams; |
100 | 0 | return cx; |
101 | 0 | loser: |
102 | 0 | SECITEM_FreeItem(&mechparams, PR_FALSE); |
103 | 0 | return NULL; |
104 | 0 | } |
105 | | |
106 | | SGNContext * |
107 | | SGN_NewContext(SECOidTag alg, SECKEYPrivateKey *key) |
108 | 0 | { |
109 | 0 | return sgn_NewContext(alg, NULL, key); |
110 | 0 | } |
111 | | |
112 | | SGNContext * |
113 | | SGN_NewContextWithAlgorithmID(SECAlgorithmID *alg, SECKEYPrivateKey *key) |
114 | 0 | { |
115 | 0 | SECOidTag tag = SECOID_GetAlgorithmTag(alg); |
116 | 0 | return sgn_NewContext(tag, &alg->parameters, key); |
117 | 0 | } |
118 | | |
119 | | void |
120 | | SGN_DestroyContext(SGNContext *cx, PRBool freeit) |
121 | 0 | { |
122 | 0 | if (cx) { |
123 | 0 | if (cx->hashcx != NULL) { |
124 | 0 | (*cx->hashobj->destroy)(cx->hashcx, PR_TRUE); |
125 | 0 | cx->hashcx = NULL; |
126 | 0 | } |
127 | 0 | if (cx->signcx != NULL) { |
128 | 0 | PK11_DestroyContext(cx->signcx, PR_TRUE); |
129 | 0 | cx->signcx = NULL; |
130 | 0 | } |
131 | 0 | SECITEM_FreeItem(&cx->mechparams, PR_FALSE); |
132 | 0 | if (freeit) { |
133 | 0 | PORT_ZFree(cx, sizeof(SGNContext)); |
134 | 0 | } |
135 | 0 | } |
136 | 0 | } |
137 | | |
138 | | static PK11Context * |
139 | | sgn_CreateCombinedContext(SGNContext *cx) |
140 | 0 | { |
141 | | /* the particular combination of hash and signature doesn't have a combined |
142 | | * mechanism, fall back to hand hash & sign */ |
143 | 0 | if (cx->mech == CKM_INVALID_MECHANISM) { |
144 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
145 | 0 | return NULL; |
146 | 0 | } |
147 | | /* the token the private key resides in doesn't support the combined |
148 | | * mechanism, fall back to hand hash & sign */ |
149 | 0 | if (!PK11_DoesMechanismFlag(cx->key->pkcs11Slot, cx->mech, CKF_SIGN)) { |
150 | 0 | PORT_SetError(SEC_ERROR_NO_TOKEN); |
151 | 0 | return NULL; |
152 | 0 | } |
153 | 0 | return PK11_CreateContextByPrivKey(cx->mech, CKA_SIGN, cx->key, |
154 | 0 | &cx->mechparams); |
155 | 0 | } |
156 | | |
157 | | SECStatus |
158 | | SGN_Begin(SGNContext *cx) |
159 | 0 | { |
160 | 0 | PK11Context *signcx = NULL; |
161 | |
|
162 | 0 | if (cx->hashcx != NULL) { |
163 | 0 | (*cx->hashobj->destroy)(cx->hashcx, PR_TRUE); |
164 | 0 | cx->hashcx = NULL; |
165 | 0 | } |
166 | 0 | if (cx->signcx != NULL) { |
167 | 0 | (void)PK11_DestroyContext(cx->signcx, PR_TRUE); |
168 | 0 | cx->signcx = NULL; |
169 | 0 | } |
170 | | /* if we can get a combined context, we'll use that */ |
171 | 0 | signcx = sgn_CreateCombinedContext(cx); |
172 | 0 | if (signcx != NULL) { |
173 | 0 | cx->signcx = signcx; |
174 | 0 | return SECSuccess; |
175 | 0 | } |
176 | | |
177 | 0 | cx->hashobj = HASH_GetHashObjectByOidTag(cx->hashalg); |
178 | 0 | if (!cx->hashobj) |
179 | 0 | return SECFailure; /* error code is already set */ |
180 | | |
181 | 0 | cx->hashcx = (*cx->hashobj->create)(); |
182 | 0 | if (cx->hashcx == NULL) |
183 | 0 | return SECFailure; |
184 | | |
185 | 0 | (*cx->hashobj->begin)(cx->hashcx); |
186 | 0 | return SECSuccess; |
187 | 0 | } |
188 | | |
189 | | SECStatus |
190 | | SGN_Update(SGNContext *cx, const unsigned char *input, unsigned int inputLen) |
191 | 0 | { |
192 | 0 | if (cx->hashcx == NULL) { |
193 | 0 | if (cx->signcx == NULL) { |
194 | 0 | PORT_SetError(SEC_ERROR_INVALID_ARGS); |
195 | 0 | return SECFailure; |
196 | 0 | } |
197 | 0 | return PK11_DigestOp(cx->signcx, input, inputLen); |
198 | 0 | } |
199 | 0 | (*cx->hashobj->update)(cx->hashcx, input, inputLen); |
200 | 0 | return SECSuccess; |
201 | 0 | } |
202 | | |
203 | | /* XXX Old template; want to expunge it eventually. */ |
204 | | static DERTemplate SECAlgorithmIDTemplate[] = { |
205 | | { DER_SEQUENCE, |
206 | | 0, NULL, sizeof(SECAlgorithmID) }, |
207 | | { DER_OBJECT_ID, |
208 | | offsetof(SECAlgorithmID, algorithm) }, |
209 | | { DER_OPTIONAL | DER_ANY, |
210 | | offsetof(SECAlgorithmID, parameters) }, |
211 | | { 0 } |
212 | | }; |
213 | | |
214 | | /* |
215 | | * XXX OLD Template. Once all uses have been switched over to new one, |
216 | | * remove this. |
217 | | */ |
218 | | static DERTemplate SGNDigestInfoTemplate[] = { |
219 | | { DER_SEQUENCE, |
220 | | 0, NULL, sizeof(SGNDigestInfo) }, |
221 | | { DER_INLINE, |
222 | | offsetof(SGNDigestInfo, digestAlgorithm), |
223 | | SECAlgorithmIDTemplate }, |
224 | | { DER_OCTET_STRING, |
225 | | offsetof(SGNDigestInfo, digest) }, |
226 | | { 0 } |
227 | | }; |
228 | | |
229 | | static SECStatus |
230 | | sgn_allocateSignatureItem(SECKEYPrivateKey *privKey, SECItem *sigitem) |
231 | 0 | { |
232 | 0 | int signatureLen; |
233 | 0 | signatureLen = PK11_SignatureLen(privKey); |
234 | 0 | if (signatureLen <= 0) { |
235 | 0 | PORT_SetError(SEC_ERROR_INVALID_KEY); |
236 | 0 | return SECFailure; |
237 | 0 | } |
238 | 0 | sigitem->len = signatureLen; |
239 | 0 | sigitem->data = (unsigned char *)PORT_Alloc(signatureLen); |
240 | |
|
241 | 0 | if (sigitem->data == NULL) { |
242 | 0 | PORT_SetError(SEC_ERROR_NO_MEMORY); |
243 | 0 | return SECFailure; |
244 | 0 | } |
245 | 0 | return SECSuccess; |
246 | 0 | } |
247 | | |
248 | | /* Sometimes the DER signature format for the signature is different than |
249 | | * The PKCS #11 format for the signature. This code handles the correction |
250 | | * from PKCS #11 to DER */ |
251 | | static SECStatus |
252 | | sgn_PKCS11ToX509Sig(SGNContext *cx, SECItem *sigitem) |
253 | 0 | { |
254 | 0 | SECStatus rv; |
255 | 0 | SECItem result = { siBuffer, NULL, 0 }; |
256 | |
|
257 | 0 | if ((cx->signalg == SEC_OID_ANSIX9_DSA_SIGNATURE) || |
258 | 0 | (cx->signalg == SEC_OID_ANSIX962_EC_PUBLIC_KEY)) { |
259 | | /* DSAU_EncodeDerSigWithLen works for DSA and ECDSA */ |
260 | 0 | rv = DSAU_EncodeDerSigWithLen(&result, sigitem, sigitem->len); |
261 | | /* we are done with sigItem. In case of failure, we want to free |
262 | | * it anyway */ |
263 | 0 | SECITEM_FreeItem(sigitem, PR_FALSE); |
264 | 0 | if (rv != SECSuccess) { |
265 | 0 | return rv; |
266 | 0 | } |
267 | 0 | *sigitem = result; |
268 | 0 | } |
269 | 0 | return SECSuccess; |
270 | 0 | } |
271 | | |
272 | | SECStatus |
273 | | SGN_End(SGNContext *cx, SECItem *result) |
274 | 0 | { |
275 | 0 | unsigned char digest[HASH_LENGTH_MAX]; |
276 | 0 | unsigned part1; |
277 | 0 | SECStatus rv; |
278 | 0 | SECItem digder, sigitem; |
279 | 0 | PLArenaPool *arena = 0; |
280 | 0 | SECKEYPrivateKey *privKey = cx->key; |
281 | 0 | SGNDigestInfo *di = 0; |
282 | |
|
283 | 0 | result->data = 0; |
284 | 0 | digder.data = 0; |
285 | 0 | sigitem.data = 0; |
286 | |
|
287 | 0 | if (cx->hashcx == NULL) { |
288 | 0 | if (cx->signcx == NULL) { |
289 | 0 | PORT_SetError(SEC_ERROR_INVALID_ARGS); |
290 | 0 | return SECFailure; |
291 | 0 | } |
292 | | /* if we are doing the combined hash/sign function, just finalize |
293 | | * the signature */ |
294 | 0 | rv = sgn_allocateSignatureItem(privKey, result); |
295 | 0 | if (rv != SECSuccess) { |
296 | 0 | return rv; |
297 | 0 | } |
298 | 0 | rv = PK11_DigestFinal(cx->signcx, result->data, &result->len, |
299 | 0 | result->len); |
300 | 0 | if (rv != SECSuccess) { |
301 | 0 | SECITEM_ZfreeItem(result, PR_FALSE); |
302 | 0 | result->data = NULL; |
303 | 0 | return rv; |
304 | 0 | } |
305 | 0 | return sgn_PKCS11ToX509Sig(cx, result); |
306 | 0 | } |
307 | | /* Finish up digest function */ |
308 | 0 | (*cx->hashobj->end)(cx->hashcx, digest, &part1, sizeof(digest)); |
309 | |
|
310 | 0 | if (privKey->keyType == rsaKey && |
311 | 0 | cx->signalg != SEC_OID_PKCS1_RSA_PSS_SIGNATURE) { |
312 | |
|
313 | 0 | arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
314 | 0 | if (!arena) { |
315 | 0 | rv = SECFailure; |
316 | 0 | goto loser; |
317 | 0 | } |
318 | | |
319 | | /* Construct digest info */ |
320 | 0 | di = SGN_CreateDigestInfo(cx->hashalg, digest, part1); |
321 | 0 | if (!di) { |
322 | 0 | rv = SECFailure; |
323 | 0 | goto loser; |
324 | 0 | } |
325 | | |
326 | | /* Der encode the digest as a DigestInfo */ |
327 | 0 | rv = DER_Encode(arena, &digder, SGNDigestInfoTemplate, |
328 | 0 | di); |
329 | 0 | if (rv != SECSuccess) { |
330 | 0 | goto loser; |
331 | 0 | } |
332 | 0 | } else { |
333 | 0 | digder.data = digest; |
334 | 0 | digder.len = part1; |
335 | 0 | } |
336 | | |
337 | | /* |
338 | | ** Encrypt signature after constructing appropriate PKCS#1 signature |
339 | | ** block |
340 | | */ |
341 | 0 | rv = sgn_allocateSignatureItem(privKey, &sigitem); |
342 | 0 | if (rv != SECSuccess) { |
343 | 0 | return rv; |
344 | 0 | } |
345 | | |
346 | 0 | if (cx->signalg == SEC_OID_PKCS1_RSA_PSS_SIGNATURE) { |
347 | 0 | rv = PK11_SignWithMechanism(privKey, CKM_RSA_PKCS_PSS, &cx->mechparams, |
348 | 0 | &sigitem, &digder); |
349 | 0 | if (rv != SECSuccess) { |
350 | 0 | goto loser; |
351 | 0 | } |
352 | 0 | } else { |
353 | 0 | rv = PK11_Sign(privKey, &sigitem, &digder); |
354 | 0 | if (rv != SECSuccess) { |
355 | 0 | goto loser; |
356 | 0 | } |
357 | 0 | } |
358 | 0 | rv = sgn_PKCS11ToX509Sig(cx, &sigitem); |
359 | 0 | *result = sigitem; |
360 | 0 | if (rv != SECSuccess) { |
361 | 0 | goto loser; |
362 | 0 | } |
363 | 0 | return SECSuccess; |
364 | | |
365 | 0 | loser: |
366 | 0 | if (rv != SECSuccess) { |
367 | 0 | SECITEM_FreeItem(&sigitem, PR_FALSE); |
368 | 0 | } |
369 | 0 | SGN_DestroyDigestInfo(di); |
370 | 0 | if (arena != NULL) { |
371 | 0 | PORT_FreeArena(arena, PR_FALSE); |
372 | 0 | } |
373 | 0 | return rv; |
374 | 0 | } |
375 | | |
376 | | static SECStatus |
377 | | sgn_SingleShot(SGNContext *cx, const unsigned char *input, |
378 | | unsigned int inputLen, SECItem *result) |
379 | 0 | { |
380 | 0 | SECStatus rv; |
381 | |
|
382 | 0 | result->data = 0; |
383 | | /* if we have the combined mechanism, just do the single shot |
384 | | * version */ |
385 | 0 | if ((cx->mech != CKM_INVALID_MECHANISM) && |
386 | 0 | PK11_DoesMechanismFlag(cx->key->pkcs11Slot, cx->mech, CKF_SIGN)) { |
387 | 0 | SECItem data = { siBuffer, (unsigned char *)input, inputLen }; |
388 | 0 | rv = sgn_allocateSignatureItem(cx->key, result); |
389 | 0 | if (rv != SECSuccess) { |
390 | 0 | return rv; |
391 | 0 | } |
392 | 0 | rv = PK11_SignWithMechanism(cx->key, cx->mech, &cx->mechparams, |
393 | 0 | result, &data); |
394 | 0 | if (rv != SECSuccess) { |
395 | 0 | SECITEM_ZfreeItem(result, PR_FALSE); |
396 | 0 | return rv; |
397 | 0 | } |
398 | 0 | return sgn_PKCS11ToX509Sig(cx, result); |
399 | 0 | } |
400 | | /* fall back to the stream version */ |
401 | 0 | rv = SGN_Begin(cx); |
402 | 0 | if (rv != SECSuccess) |
403 | 0 | return rv; |
404 | | |
405 | 0 | rv = SGN_Update(cx, input, inputLen); |
406 | 0 | if (rv != SECSuccess) |
407 | 0 | return rv; |
408 | | |
409 | 0 | return SGN_End(cx, result); |
410 | 0 | } |
411 | | |
412 | | /************************************************************************/ |
413 | | |
414 | | static SECStatus |
415 | | sec_SignData(SECItem *res, const unsigned char *buf, int len, |
416 | | SECKEYPrivateKey *pk, SECOidTag algid, SECItem *params) |
417 | 0 | { |
418 | 0 | SECStatus rv; |
419 | 0 | SGNContext *sgn; |
420 | |
|
421 | 0 | sgn = sgn_NewContext(algid, params, pk); |
422 | |
|
423 | 0 | if (sgn == NULL) |
424 | 0 | return SECFailure; |
425 | | |
426 | 0 | rv = sgn_SingleShot(sgn, buf, len, res); |
427 | 0 | if (rv != SECSuccess) |
428 | 0 | goto loser; |
429 | | |
430 | 0 | loser: |
431 | 0 | SGN_DestroyContext(sgn, PR_TRUE); |
432 | 0 | return rv; |
433 | 0 | } |
434 | | |
435 | | /* |
436 | | ** Sign a block of data returning in result a bunch of bytes that are the |
437 | | ** signature. Returns zero on success, an error code on failure. |
438 | | */ |
439 | | SECStatus |
440 | | SEC_SignData(SECItem *res, const unsigned char *buf, int len, |
441 | | SECKEYPrivateKey *pk, SECOidTag algid) |
442 | 0 | { |
443 | 0 | return sec_SignData(res, buf, len, pk, algid, NULL); |
444 | 0 | } |
445 | | |
446 | | SECStatus |
447 | | SEC_SignDataWithAlgorithmID(SECItem *res, const unsigned char *buf, int len, |
448 | | SECKEYPrivateKey *pk, SECAlgorithmID *algid) |
449 | 0 | { |
450 | 0 | SECOidTag tag = SECOID_GetAlgorithmTag(algid); |
451 | 0 | return sec_SignData(res, buf, len, pk, tag, &algid->parameters); |
452 | 0 | } |
453 | | |
454 | | /************************************************************************/ |
455 | | |
456 | | DERTemplate CERTSignedDataTemplate[] = { |
457 | | { DER_SEQUENCE, |
458 | | 0, NULL, sizeof(CERTSignedData) }, |
459 | | { DER_ANY, |
460 | | offsetof(CERTSignedData, data) }, |
461 | | { DER_INLINE, |
462 | | offsetof(CERTSignedData, signatureAlgorithm), |
463 | | SECAlgorithmIDTemplate }, |
464 | | { DER_BIT_STRING, |
465 | | offsetof(CERTSignedData, signature) }, |
466 | | { 0 } |
467 | | }; |
468 | | |
469 | | SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) |
470 | | |
471 | | const SEC_ASN1Template CERT_SignedDataTemplate[] = { |
472 | | { SEC_ASN1_SEQUENCE, |
473 | | 0, NULL, sizeof(CERTSignedData) }, |
474 | | { SEC_ASN1_ANY, |
475 | | offsetof(CERTSignedData, data) }, |
476 | | { SEC_ASN1_INLINE | SEC_ASN1_XTRN, |
477 | | offsetof(CERTSignedData, signatureAlgorithm), |
478 | | SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
479 | | { SEC_ASN1_BIT_STRING, |
480 | | offsetof(CERTSignedData, signature) }, |
481 | | { 0 } |
482 | | }; |
483 | | |
484 | | SEC_ASN1_CHOOSER_IMPLEMENT(CERT_SignedDataTemplate) |
485 | | |
486 | | static SECStatus |
487 | | sec_DerSignData(PLArenaPool *arena, SECItem *result, |
488 | | const unsigned char *buf, int len, SECKEYPrivateKey *pk, |
489 | | SECOidTag algID, SECItem *params) |
490 | 0 | { |
491 | 0 | SECItem it; |
492 | 0 | CERTSignedData sd; |
493 | 0 | SECStatus rv; |
494 | |
|
495 | 0 | it.data = 0; |
496 | | |
497 | | /* XXX We should probably have some asserts here to make sure the key type |
498 | | * and algID match |
499 | | */ |
500 | |
|
501 | 0 | if (algID == SEC_OID_UNKNOWN) { |
502 | 0 | switch (pk->keyType) { |
503 | 0 | case rsaKey: |
504 | 0 | algID = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; |
505 | 0 | break; |
506 | 0 | case dsaKey: |
507 | | /* get Signature length (= q_len*2) and work from there */ |
508 | 0 | switch (PK11_SignatureLen(pk)) { |
509 | 0 | case 320: |
510 | 0 | algID = SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST; |
511 | 0 | break; |
512 | 0 | case 448: |
513 | 0 | algID = SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST; |
514 | 0 | break; |
515 | 0 | case 512: |
516 | 0 | default: |
517 | 0 | algID = SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST; |
518 | 0 | break; |
519 | 0 | } |
520 | 0 | break; |
521 | 0 | case ecKey: |
522 | 0 | algID = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; |
523 | 0 | break; |
524 | 0 | default: |
525 | 0 | PORT_SetError(SEC_ERROR_INVALID_KEY); |
526 | 0 | return SECFailure; |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | /* Sign input buffer */ |
531 | 0 | rv = sec_SignData(&it, buf, len, pk, algID, params); |
532 | 0 | if (rv) |
533 | 0 | goto loser; |
534 | | |
535 | | /* Fill out SignedData object */ |
536 | 0 | PORT_Memset(&sd, 0, sizeof(sd)); |
537 | 0 | sd.data.data = (unsigned char *)buf; |
538 | 0 | sd.data.len = len; |
539 | 0 | sd.signature.data = it.data; |
540 | 0 | sd.signature.len = it.len << 3; /* convert to bit string */ |
541 | 0 | rv = SECOID_SetAlgorithmID(arena, &sd.signatureAlgorithm, algID, params); |
542 | 0 | if (rv) |
543 | 0 | goto loser; |
544 | | |
545 | | /* DER encode the signed data object */ |
546 | 0 | rv = DER_Encode(arena, result, CERTSignedDataTemplate, &sd); |
547 | | /* FALL THROUGH */ |
548 | |
|
549 | 0 | loser: |
550 | 0 | PORT_Free(it.data); |
551 | 0 | return rv; |
552 | 0 | } |
553 | | |
554 | | SECStatus |
555 | | SEC_DerSignData(PLArenaPool *arena, SECItem *result, |
556 | | const unsigned char *buf, int len, SECKEYPrivateKey *pk, |
557 | | SECOidTag algID) |
558 | 0 | { |
559 | 0 | return sec_DerSignData(arena, result, buf, len, pk, algID, NULL); |
560 | 0 | } |
561 | | |
562 | | SECStatus |
563 | | SEC_DerSignDataWithAlgorithmID(PLArenaPool *arena, SECItem *result, |
564 | | const unsigned char *buf, int len, |
565 | | SECKEYPrivateKey *pk, |
566 | | SECAlgorithmID *algID) |
567 | 0 | { |
568 | 0 | SECOidTag tag = SECOID_GetAlgorithmTag(algID); |
569 | 0 | return sec_DerSignData(arena, result, buf, len, pk, tag, &algID->parameters); |
570 | 0 | } |
571 | | |
572 | | SECStatus |
573 | | SGN_Digest(SECKEYPrivateKey *privKey, |
574 | | SECOidTag algtag, SECItem *result, SECItem *digest) |
575 | 0 | { |
576 | 0 | int modulusLen; |
577 | 0 | SECStatus rv; |
578 | 0 | SECItem digder; |
579 | 0 | PLArenaPool *arena = 0; |
580 | 0 | SGNDigestInfo *di = 0; |
581 | 0 | SECOidTag enctag; |
582 | 0 | PRUint32 policyFlags; |
583 | 0 | PRInt32 optFlags; |
584 | |
|
585 | 0 | result->data = 0; |
586 | |
|
587 | 0 | if (NSS_OptionGet(NSS_KEY_SIZE_POLICY_FLAGS, &optFlags) != SECFailure) { |
588 | 0 | if (optFlags & NSS_KEY_SIZE_POLICY_SIGN_FLAG) { |
589 | 0 | rv = SECKEY_EnforceKeySize(privKey->keyType, |
590 | 0 | SECKEY_PrivateKeyStrengthInBits(privKey), |
591 | 0 | SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); |
592 | 0 | if (rv != SECSuccess) { |
593 | 0 | return SECFailure; |
594 | 0 | } |
595 | 0 | } |
596 | 0 | } |
597 | | /* check the policy on the hash algorithm */ |
598 | 0 | if ((NSS_GetAlgorithmPolicy(algtag, &policyFlags) == SECFailure) || |
599 | 0 | !(policyFlags & NSS_USE_ALG_IN_ANY_SIGNATURE)) { |
600 | 0 | PORT_SetError(SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); |
601 | 0 | return SECFailure; |
602 | 0 | } |
603 | | /* check the policy on the encryption algorithm */ |
604 | 0 | enctag = sec_GetEncAlgFromSigAlg( |
605 | 0 | SEC_GetSignatureAlgorithmOidTag(privKey->keyType, algtag)); |
606 | 0 | if ((enctag == SEC_OID_UNKNOWN) || |
607 | 0 | (NSS_GetAlgorithmPolicy(enctag, &policyFlags) == SECFailure) || |
608 | 0 | !(policyFlags & NSS_USE_ALG_IN_ANY_SIGNATURE)) { |
609 | 0 | PORT_SetError(SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); |
610 | 0 | return SECFailure; |
611 | 0 | } |
612 | | |
613 | 0 | if (privKey->keyType == rsaKey) { |
614 | |
|
615 | 0 | arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
616 | 0 | if (!arena) { |
617 | 0 | rv = SECFailure; |
618 | 0 | goto loser; |
619 | 0 | } |
620 | | |
621 | | /* Construct digest info */ |
622 | 0 | di = SGN_CreateDigestInfo(algtag, digest->data, digest->len); |
623 | 0 | if (!di) { |
624 | 0 | rv = SECFailure; |
625 | 0 | goto loser; |
626 | 0 | } |
627 | | |
628 | | /* Der encode the digest as a DigestInfo */ |
629 | 0 | rv = DER_Encode(arena, &digder, SGNDigestInfoTemplate, |
630 | 0 | di); |
631 | 0 | if (rv != SECSuccess) { |
632 | 0 | goto loser; |
633 | 0 | } |
634 | 0 | } else { |
635 | 0 | digder.data = digest->data; |
636 | 0 | digder.len = digest->len; |
637 | 0 | } |
638 | | |
639 | | /* |
640 | | ** Encrypt signature after constructing appropriate PKCS#1 signature |
641 | | ** block |
642 | | */ |
643 | 0 | modulusLen = PK11_SignatureLen(privKey); |
644 | 0 | if (modulusLen <= 0) { |
645 | 0 | PORT_SetError(SEC_ERROR_INVALID_KEY); |
646 | 0 | rv = SECFailure; |
647 | 0 | goto loser; |
648 | 0 | } |
649 | 0 | result->len = modulusLen; |
650 | 0 | result->data = (unsigned char *)PORT_Alloc(modulusLen); |
651 | 0 | result->type = siBuffer; |
652 | |
|
653 | 0 | if (result->data == NULL) { |
654 | 0 | rv = SECFailure; |
655 | 0 | goto loser; |
656 | 0 | } |
657 | | |
658 | 0 | rv = PK11_Sign(privKey, result, &digder); |
659 | 0 | if (rv != SECSuccess) { |
660 | 0 | PORT_Free(result->data); |
661 | 0 | result->data = NULL; |
662 | 0 | } |
663 | |
|
664 | 0 | loser: |
665 | 0 | SGN_DestroyDigestInfo(di); |
666 | 0 | if (arena != NULL) { |
667 | 0 | PORT_FreeArena(arena, PR_FALSE); |
668 | 0 | } |
669 | 0 | return rv; |
670 | 0 | } |
671 | | |
672 | | SECOidTag |
673 | | SEC_GetSignatureAlgorithmOidTag(KeyType keyType, SECOidTag hashAlgTag) |
674 | 0 | { |
675 | 0 | SECOidTag sigTag = SEC_OID_UNKNOWN; |
676 | |
|
677 | 0 | switch (keyType) { |
678 | 0 | case rsaKey: |
679 | 0 | switch (hashAlgTag) { |
680 | 0 | case SEC_OID_MD2: |
681 | 0 | sigTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION; |
682 | 0 | break; |
683 | 0 | case SEC_OID_MD5: |
684 | 0 | sigTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; |
685 | 0 | break; |
686 | 0 | case SEC_OID_SHA1: |
687 | 0 | sigTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; |
688 | 0 | break; |
689 | 0 | case SEC_OID_SHA224: |
690 | 0 | sigTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION; |
691 | 0 | break; |
692 | 0 | case SEC_OID_UNKNOWN: /* default for RSA if not specified */ |
693 | 0 | case SEC_OID_SHA256: |
694 | 0 | sigTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; |
695 | 0 | break; |
696 | 0 | case SEC_OID_SHA384: |
697 | 0 | sigTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; |
698 | 0 | break; |
699 | 0 | case SEC_OID_SHA512: |
700 | 0 | sigTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; |
701 | 0 | break; |
702 | 0 | default: |
703 | 0 | break; |
704 | 0 | } |
705 | 0 | break; |
706 | 0 | case dsaKey: |
707 | 0 | switch (hashAlgTag) { |
708 | 0 | case SEC_OID_SHA1: |
709 | 0 | sigTag = SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST; |
710 | 0 | break; |
711 | 0 | case SEC_OID_SHA224: |
712 | 0 | sigTag = SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST; |
713 | 0 | break; |
714 | 0 | case SEC_OID_UNKNOWN: /* default for DSA if not specified */ |
715 | 0 | case SEC_OID_SHA256: |
716 | 0 | sigTag = SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST; |
717 | 0 | break; |
718 | 0 | default: |
719 | 0 | break; |
720 | 0 | } |
721 | 0 | break; |
722 | 0 | case ecKey: |
723 | 0 | switch (hashAlgTag) { |
724 | 0 | case SEC_OID_SHA1: |
725 | 0 | sigTag = SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE; |
726 | 0 | break; |
727 | 0 | case SEC_OID_SHA224: |
728 | 0 | sigTag = SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE; |
729 | 0 | break; |
730 | 0 | case SEC_OID_UNKNOWN: /* default for ECDSA if not specified */ |
731 | 0 | case SEC_OID_SHA256: |
732 | 0 | sigTag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; |
733 | 0 | break; |
734 | 0 | case SEC_OID_SHA384: |
735 | 0 | sigTag = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE; |
736 | 0 | break; |
737 | 0 | case SEC_OID_SHA512: |
738 | 0 | sigTag = SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE; |
739 | 0 | break; |
740 | 0 | default: |
741 | 0 | break; |
742 | 0 | } |
743 | 0 | default: |
744 | 0 | break; |
745 | 0 | } |
746 | 0 | return sigTag; |
747 | 0 | } |
748 | | |
749 | | static SECItem * |
750 | | sec_CreateRSAPSSParameters(PLArenaPool *arena, |
751 | | SECItem *result, |
752 | | SECOidTag hashAlgTag, |
753 | | const SECItem *params, |
754 | | const SECKEYPrivateKey *key) |
755 | 0 | { |
756 | 0 | SECKEYRSAPSSParams pssParams; |
757 | 0 | int modBytes, hashLength; |
758 | 0 | unsigned long saltLength; |
759 | 0 | PRBool defaultSHA1 = PR_FALSE; |
760 | 0 | SECStatus rv; |
761 | |
|
762 | 0 | if (key->keyType != rsaKey && key->keyType != rsaPssKey) { |
763 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
764 | 0 | return NULL; |
765 | 0 | } |
766 | | |
767 | 0 | PORT_Memset(&pssParams, 0, sizeof(pssParams)); |
768 | |
|
769 | 0 | if (params && params->data) { |
770 | | /* The parameters field should either be empty or contain |
771 | | * valid RSA-PSS parameters */ |
772 | 0 | PORT_Assert(!(params->len == 2 && |
773 | 0 | params->data[0] == SEC_ASN1_NULL && |
774 | 0 | params->data[1] == 0)); |
775 | 0 | rv = SEC_QuickDERDecodeItem(arena, &pssParams, |
776 | 0 | SECKEY_RSAPSSParamsTemplate, |
777 | 0 | params); |
778 | 0 | if (rv != SECSuccess) { |
779 | 0 | return NULL; |
780 | 0 | } |
781 | 0 | defaultSHA1 = PR_TRUE; |
782 | 0 | } |
783 | | |
784 | 0 | if (pssParams.trailerField.data) { |
785 | 0 | unsigned long trailerField; |
786 | |
|
787 | 0 | rv = SEC_ASN1DecodeInteger((SECItem *)&pssParams.trailerField, |
788 | 0 | &trailerField); |
789 | 0 | if (rv != SECSuccess) { |
790 | 0 | return NULL; |
791 | 0 | } |
792 | 0 | if (trailerField != 1) { |
793 | 0 | PORT_SetError(SEC_ERROR_INVALID_ARGS); |
794 | 0 | return NULL; |
795 | 0 | } |
796 | 0 | } |
797 | | |
798 | 0 | modBytes = PK11_GetPrivateModulusLen((SECKEYPrivateKey *)key); |
799 | | |
800 | | /* Determine the hash algorithm to use, based on hashAlgTag and |
801 | | * pssParams.hashAlg; there are four cases */ |
802 | 0 | if (hashAlgTag != SEC_OID_UNKNOWN) { |
803 | 0 | SECOidTag tag = SEC_OID_UNKNOWN; |
804 | |
|
805 | 0 | if (pssParams.hashAlg) { |
806 | 0 | tag = SECOID_GetAlgorithmTag(pssParams.hashAlg); |
807 | 0 | } else if (defaultSHA1) { |
808 | 0 | tag = SEC_OID_SHA1; |
809 | 0 | } |
810 | |
|
811 | 0 | if (tag != SEC_OID_UNKNOWN && tag != hashAlgTag) { |
812 | 0 | PORT_SetError(SEC_ERROR_INVALID_ARGS); |
813 | 0 | return NULL; |
814 | 0 | } |
815 | 0 | } else if (hashAlgTag == SEC_OID_UNKNOWN) { |
816 | 0 | if (pssParams.hashAlg) { |
817 | 0 | hashAlgTag = SECOID_GetAlgorithmTag(pssParams.hashAlg); |
818 | 0 | } else if (defaultSHA1) { |
819 | 0 | hashAlgTag = SEC_OID_SHA1; |
820 | 0 | } else { |
821 | | /* Find a suitable hash algorithm based on the NIST recommendation */ |
822 | 0 | if (modBytes <= 384) { /* 128, in NIST 800-57, Part 1 */ |
823 | 0 | hashAlgTag = SEC_OID_SHA256; |
824 | 0 | } else if (modBytes <= 960) { /* 192, NIST 800-57, Part 1 */ |
825 | 0 | hashAlgTag = SEC_OID_SHA384; |
826 | 0 | } else { |
827 | 0 | hashAlgTag = SEC_OID_SHA512; |
828 | 0 | } |
829 | 0 | } |
830 | 0 | } |
831 | | |
832 | 0 | if (hashAlgTag != SEC_OID_SHA1 && hashAlgTag != SEC_OID_SHA224 && |
833 | 0 | hashAlgTag != SEC_OID_SHA256 && hashAlgTag != SEC_OID_SHA384 && |
834 | 0 | hashAlgTag != SEC_OID_SHA512) { |
835 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
836 | 0 | return NULL; |
837 | 0 | } |
838 | | |
839 | | /* Now that the hash algorithm is decided, check if it matches the |
840 | | * existing parameters if any */ |
841 | 0 | if (pssParams.maskAlg) { |
842 | 0 | SECAlgorithmID maskHashAlg; |
843 | |
|
844 | 0 | if (SECOID_GetAlgorithmTag(pssParams.maskAlg) != SEC_OID_PKCS1_MGF1) { |
845 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
846 | 0 | return NULL; |
847 | 0 | } |
848 | | |
849 | 0 | if (pssParams.maskAlg->parameters.data == NULL) { |
850 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
851 | 0 | return NULL; |
852 | 0 | } |
853 | | |
854 | 0 | PORT_Memset(&maskHashAlg, 0, sizeof(maskHashAlg)); |
855 | 0 | rv = SEC_QuickDERDecodeItem(arena, &maskHashAlg, |
856 | 0 | SEC_ASN1_GET(SECOID_AlgorithmIDTemplate), |
857 | 0 | &pssParams.maskAlg->parameters); |
858 | 0 | if (rv != SECSuccess) { |
859 | 0 | return NULL; |
860 | 0 | } |
861 | | |
862 | | /* Following the recommendation in RFC 4055, assume the hash |
863 | | * algorithm identical to pssParam.hashAlg */ |
864 | 0 | if (SECOID_GetAlgorithmTag(&maskHashAlg) != hashAlgTag) { |
865 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
866 | 0 | return NULL; |
867 | 0 | } |
868 | 0 | } else if (defaultSHA1) { |
869 | 0 | if (hashAlgTag != SEC_OID_SHA1) { |
870 | 0 | PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); |
871 | 0 | return NULL; |
872 | 0 | } |
873 | 0 | } |
874 | | |
875 | 0 | hashLength = HASH_ResultLenByOidTag(hashAlgTag); |
876 | |
|
877 | 0 | if (pssParams.saltLength.data) { |
878 | 0 | rv = SEC_ASN1DecodeInteger((SECItem *)&pssParams.saltLength, |
879 | 0 | &saltLength); |
880 | 0 | if (rv != SECSuccess) { |
881 | 0 | return NULL; |
882 | 0 | } |
883 | | |
884 | | /* The specified salt length is too long */ |
885 | 0 | if (saltLength > (unsigned long)(modBytes - hashLength - 2)) { |
886 | 0 | PORT_SetError(SEC_ERROR_INVALID_ARGS); |
887 | 0 | return NULL; |
888 | 0 | } |
889 | 0 | } else if (defaultSHA1) { |
890 | 0 | saltLength = 20; |
891 | 0 | } |
892 | | |
893 | | /* Fill in the parameters */ |
894 | 0 | if (pssParams.hashAlg) { |
895 | 0 | if (hashAlgTag == SEC_OID_SHA1) { |
896 | | /* Omit hashAlg if the the algorithm is SHA-1 (default) */ |
897 | 0 | pssParams.hashAlg = NULL; |
898 | 0 | } |
899 | 0 | } else { |
900 | 0 | if (hashAlgTag != SEC_OID_SHA1) { |
901 | 0 | pssParams.hashAlg = PORT_ArenaZAlloc(arena, sizeof(SECAlgorithmID)); |
902 | 0 | if (!pssParams.hashAlg) { |
903 | 0 | return NULL; |
904 | 0 | } |
905 | 0 | rv = SECOID_SetAlgorithmID(arena, pssParams.hashAlg, hashAlgTag, |
906 | 0 | NULL); |
907 | 0 | if (rv != SECSuccess) { |
908 | 0 | return NULL; |
909 | 0 | } |
910 | 0 | } |
911 | 0 | } |
912 | | |
913 | 0 | if (pssParams.maskAlg) { |
914 | 0 | if (hashAlgTag == SEC_OID_SHA1) { |
915 | | /* Omit maskAlg if the the algorithm is SHA-1 (default) */ |
916 | 0 | pssParams.maskAlg = NULL; |
917 | 0 | } |
918 | 0 | } else { |
919 | 0 | if (hashAlgTag != SEC_OID_SHA1) { |
920 | 0 | SECItem *hashAlgItem; |
921 | |
|
922 | 0 | PORT_Assert(pssParams.hashAlg != NULL); |
923 | |
|
924 | 0 | hashAlgItem = SEC_ASN1EncodeItem(arena, NULL, pssParams.hashAlg, |
925 | 0 | SEC_ASN1_GET(SECOID_AlgorithmIDTemplate)); |
926 | 0 | if (!hashAlgItem) { |
927 | 0 | return NULL; |
928 | 0 | } |
929 | 0 | pssParams.maskAlg = PORT_ArenaZAlloc(arena, sizeof(SECAlgorithmID)); |
930 | 0 | if (!pssParams.maskAlg) { |
931 | 0 | return NULL; |
932 | 0 | } |
933 | 0 | rv = SECOID_SetAlgorithmID(arena, pssParams.maskAlg, |
934 | 0 | SEC_OID_PKCS1_MGF1, hashAlgItem); |
935 | 0 | if (rv != SECSuccess) { |
936 | 0 | return NULL; |
937 | 0 | } |
938 | 0 | } |
939 | 0 | } |
940 | | |
941 | 0 | if (pssParams.saltLength.data) { |
942 | 0 | if (saltLength == 20) { |
943 | | /* Omit the salt length if it is the default */ |
944 | 0 | pssParams.saltLength.data = NULL; |
945 | 0 | } |
946 | 0 | } else { |
947 | | /* Find a suitable length from the hash algorithm and modulus bits */ |
948 | 0 | saltLength = PR_MIN(hashLength, modBytes - hashLength - 2); |
949 | |
|
950 | 0 | if (saltLength != 20 && |
951 | 0 | !SEC_ASN1EncodeInteger(arena, &pssParams.saltLength, saltLength)) { |
952 | 0 | return NULL; |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | 0 | if (pssParams.trailerField.data) { |
957 | | /* Omit trailerField if the value is 1 (default) */ |
958 | 0 | pssParams.trailerField.data = NULL; |
959 | 0 | } |
960 | |
|
961 | 0 | return SEC_ASN1EncodeItem(arena, result, |
962 | 0 | &pssParams, SECKEY_RSAPSSParamsTemplate); |
963 | 0 | } |
964 | | |
965 | | SECItem * |
966 | | SEC_CreateSignatureAlgorithmParameters(PLArenaPool *arena, |
967 | | SECItem *result, |
968 | | SECOidTag signAlgTag, |
969 | | SECOidTag hashAlgTag, |
970 | | const SECItem *params, |
971 | | const SECKEYPrivateKey *key) |
972 | 0 | { |
973 | 0 | switch (signAlgTag) { |
974 | 0 | case SEC_OID_PKCS1_RSA_PSS_SIGNATURE: |
975 | 0 | return sec_CreateRSAPSSParameters(arena, result, |
976 | 0 | hashAlgTag, params, key); |
977 | | |
978 | 0 | default: |
979 | 0 | if (params == NULL) |
980 | 0 | return NULL; |
981 | 0 | if (result == NULL) |
982 | 0 | result = SECITEM_AllocItem(arena, NULL, 0); |
983 | 0 | if (SECITEM_CopyItem(arena, result, params) != SECSuccess) |
984 | 0 | return NULL; |
985 | 0 | return result; |
986 | 0 | } |
987 | 0 | } |