Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "seccomon.h"
6 #include "cert.h"
7 #include "secutil.h"
8 #include "nspr.h"
9 #include "nss.h"
10 #include "blapi.h"
11 #include "plgetopt.h"
12 #include "lowkeyi.h"
13 #include "pk11pub.h"
16 #define DEFAULT_ITERS 10
17 #define DEFAULT_DURATION 10
18 #define DEFAULT_KEY_BITS 1024
19 #define MIN_KEY_BITS 512
20 #define MAX_KEY_BITS 65536
21 #define BUFFER_BYTES MAX_KEY_BITS / 8
22 #define DEFAULT_THREADS 1
23 #define DEFAULT_EXPONENT 0x10001
25 extern NSSLOWKEYPrivateKey * getDefaultRSAPrivateKey(void);
26 extern NSSLOWKEYPublicKey * getDefaultRSAPublicKey(void);
28 secuPWData pwData = { PW_NONE, NULL };
30 typedef struct TimingContextStr TimingContext;
32 struct TimingContextStr {
33 PRTime start;
34 PRTime end;
35 PRTime interval;
37 long days;
38 int hours;
39 int minutes;
40 int seconds;
41 int millisecs;
42 };
44 TimingContext *CreateTimingContext(void) {
45 return PORT_Alloc(sizeof(TimingContext));
46 }
48 void DestroyTimingContext(TimingContext *ctx) {
49 PORT_Free(ctx);
50 }
52 void TimingBegin(TimingContext *ctx, PRTime begin) {
53 ctx->start = begin;
54 }
56 static void timingUpdate(TimingContext *ctx) {
57 PRInt64 tmp, remaining;
58 PRInt64 L1000,L60,L24;
60 LL_I2L(L1000,1000);
61 LL_I2L(L60,60);
62 LL_I2L(L24,24);
64 LL_DIV(remaining, ctx->interval, L1000);
65 LL_MOD(tmp, remaining, L1000);
66 LL_L2I(ctx->millisecs, tmp);
67 LL_DIV(remaining, remaining, L1000);
68 LL_MOD(tmp, remaining, L60);
69 LL_L2I(ctx->seconds, tmp);
70 LL_DIV(remaining, remaining, L60);
71 LL_MOD(tmp, remaining, L60);
72 LL_L2I(ctx->minutes, tmp);
73 LL_DIV(remaining, remaining, L60);
74 LL_MOD(tmp, remaining, L24);
75 LL_L2I(ctx->hours, tmp);
76 LL_DIV(remaining, remaining, L24);
77 LL_L2I(ctx->days, remaining);
78 }
80 void TimingEnd(TimingContext *ctx, PRTime end) {
81 ctx->end = end;
82 LL_SUB(ctx->interval, ctx->end, ctx->start);
83 PORT_Assert(LL_GE_ZERO(ctx->interval));
84 timingUpdate(ctx);
85 }
87 void TimingDivide(TimingContext *ctx, int divisor) {
88 PRInt64 tmp;
90 LL_I2L(tmp, divisor);
91 LL_DIV(ctx->interval, ctx->interval, tmp);
93 timingUpdate(ctx);
94 }
96 char *TimingGenerateString(TimingContext *ctx) {
97 char *buf = NULL;
99 if (ctx->days != 0) {
100 buf = PR_sprintf_append(buf, "%d days", ctx->days);
101 }
102 if (ctx->hours != 0) {
103 if (buf != NULL) buf = PR_sprintf_append(buf, ", ");
104 buf = PR_sprintf_append(buf, "%d hours", ctx->hours);
105 }
106 if (ctx->minutes != 0) {
107 if (buf != NULL) buf = PR_sprintf_append(buf, ", ");
108 buf = PR_sprintf_append(buf, "%d minutes", ctx->minutes);
109 }
110 if (buf != NULL) buf = PR_sprintf_append(buf, ", and ");
111 if (!buf && ctx->seconds == 0) {
112 int interval;
113 LL_L2I(interval, ctx->interval);
114 if (ctx->millisecs < 100)
115 buf = PR_sprintf_append(buf, "%d microseconds", interval);
116 else
117 buf = PR_sprintf_append(buf, "%d milliseconds", ctx->millisecs);
118 } else if (ctx->millisecs == 0) {
119 buf = PR_sprintf_append(buf, "%d seconds", ctx->seconds);
120 } else {
121 buf = PR_sprintf_append(buf, "%d.%03d seconds",
122 ctx->seconds, ctx->millisecs);
123 }
124 return buf;
125 }
127 void
128 Usage(char *progName)
129 {
130 fprintf(stderr, "Usage: %s [-s | -e] [-i iterations | -p period] "
131 "[-t threads]\n[-n none [-k keylength] [ [-g] -x exponent] |\n"
132 " -n token:nickname [-d certdir] [-w password] |\n"
133 " -h token [-d certdir] [-w password] [-g] [-k keylength] "
134 "[-x exponent] [-f pwfile]\n",
135 progName);
136 fprintf(stderr, "%-20s Cert database directory (default is ~/.netscape)\n",
137 "-d certdir");
138 fprintf(stderr, "%-20s How many operations to perform\n", "-i iterations");
139 fprintf(stderr, "%-20s How many seconds to run\n", "-p period");
140 fprintf(stderr, "%-20s Perform signing (private key) operations\n", "-s");
141 fprintf(stderr, "%-20s Perform encryption (public key) operations\n","-e");
142 fprintf(stderr, "%-20s Nickname of certificate or key, prefixed "
143 "by optional token name\n", "-n nickname");
144 fprintf(stderr, "%-20s PKCS#11 token to perform operation with.\n",
145 "-h token");
146 fprintf(stderr, "%-20s key size in bits, from %d to %d\n", "-k keylength",
147 MIN_KEY_BITS, MAX_KEY_BITS);
148 fprintf(stderr, "%-20s token password\n", "-w password");
149 fprintf(stderr, "%-20s temporary key generation. Not for token keys.\n",
150 "-g");
151 fprintf(stderr, "%-20s set public exponent for keygen\n", "-x");
152 fprintf(stderr, "%-20s Number of execution threads (default 1)\n",
153 "-t threads");
154 exit(-1);
155 }
157 static void
158 dumpBytes( unsigned char * b, int l)
159 {
160 int i;
161 if (l <= 0)
162 return;
163 for (i = 0; i < l; ++i) {
164 if (i % 16 == 0)
165 printf("\t");
166 printf(" %02x", b[i]);
167 if (i % 16 == 15)
168 printf("\n");
169 }
170 if ((i % 16) != 0)
171 printf("\n");
172 }
174 static void
175 dumpItem( SECItem * item, const char * description)
176 {
177 if (item->len & 1 && item->data[0] == 0) {
178 printf("%s: (%d bytes)\n", description, item->len - 1);
179 dumpBytes(item->data + 1, item->len - 1);
180 } else {
181 printf("%s: (%d bytes)\n", description, item->len);
182 dumpBytes(item->data, item->len);
183 }
184 }
186 void
187 printPrivKey(NSSLOWKEYPrivateKey * privKey)
188 {
189 RSAPrivateKey *rsa = &privKey->u.rsa;
191 dumpItem( &rsa->modulus, "n");
192 dumpItem( &rsa->publicExponent, "e");
193 dumpItem( &rsa->privateExponent, "d");
194 dumpItem( &rsa->prime1, "P");
195 dumpItem( &rsa->prime2, "Q");
196 dumpItem( &rsa->exponent1, "d % (P-1)");
197 dumpItem( &rsa->exponent2, "d % (Q-1)");
198 dumpItem( &rsa->coefficient, "(Q ** -1) % P");
199 puts("");
200 }
202 typedef SECStatus (* RSAOp)(void * key,
203 unsigned char * output,
204 unsigned char * input);
206 typedef struct {
207 SECKEYPublicKey* pubKey;
208 SECKEYPrivateKey* privKey;
209 } PK11Keys;
212 SECStatus PK11_PublicKeyOp (SECKEYPublicKey* key,
213 unsigned char * output,
214 unsigned char * input)
215 {
216 return PK11_PubEncryptRaw(key, output, input, key->u.rsa.modulus.len,
217 NULL);
218 }
220 SECStatus PK11_PrivateKeyOp (PK11Keys* keys,
221 unsigned char * output,
222 unsigned char * input)
223 {
224 unsigned outLen = 0;
225 return PK11_PrivDecryptRaw(keys->privKey,
226 output, &outLen,
227 keys->pubKey->u.rsa.modulus.len, input,
228 keys->pubKey->u.rsa.modulus.len);
229 }
230 typedef struct ThreadRunDataStr ThreadRunData;
232 struct ThreadRunDataStr {
233 const PRBool *doIters;
234 const void *rsaKey;
235 const unsigned char *buf;
236 RSAOp fn;
237 int seconds;
238 long iters;
239 long iterRes;
240 PRErrorCode errNum;
241 SECStatus status;
242 };
245 void ThreadExecFunction(void *data)
246 {
247 ThreadRunData *tdata = (ThreadRunData*)data;
248 unsigned char buf2[BUFFER_BYTES];
250 tdata->status = SECSuccess;
251 if (*tdata->doIters) {
252 long i = tdata->iters;
253 tdata->iterRes = 0;
254 while (i--) {
255 SECStatus rv = tdata->fn((void*)tdata->rsaKey, buf2,
256 (unsigned char*)tdata->buf);
257 if (rv != SECSuccess) {
258 tdata->errNum = PORT_GetError();
259 tdata->status = rv;
260 break;
261 }
262 tdata->iterRes++;
263 }
264 } else {
265 PRIntervalTime total = PR_SecondsToInterval(tdata->seconds);
266 PRIntervalTime start = PR_IntervalNow();
267 tdata->iterRes = 0;
268 while (PR_IntervalNow() - start < total) {
269 SECStatus rv = tdata->fn((void*)tdata->rsaKey, buf2,
270 (unsigned char*)tdata->buf);
271 if (rv != SECSuccess) {
272 tdata->errNum = PORT_GetError();
273 tdata->status = rv;
274 break;
275 }
276 tdata->iterRes++;
277 }
278 }
279 }
281 #define INT_ARG(arg,def) atol(arg)>0?atol(arg):def
283 int
284 main(int argc, char **argv)
285 {
286 TimingContext * timeCtx = NULL;
287 SECKEYPublicKey * pubHighKey = NULL;
288 SECKEYPrivateKey * privHighKey = NULL;
289 NSSLOWKEYPrivateKey * privKey = NULL;
290 NSSLOWKEYPublicKey * pubKey = NULL;
291 CERTCertificate * cert = NULL;
292 char * progName = NULL;
293 char * secDir = NULL;
294 char * nickname = NULL;
295 char * slotname = NULL;
296 long keybits = 0;
297 RSAOp fn;
298 void * rsaKey = NULL;
299 PLOptState * optstate;
300 PLOptStatus optstatus;
301 long iters = DEFAULT_ITERS;
302 int i;
303 PRBool doPriv = PR_FALSE;
304 PRBool doPub = PR_FALSE;
305 int rv;
306 unsigned char buf[BUFFER_BYTES];
307 unsigned char buf2[BUFFER_BYTES];
308 int seconds = DEFAULT_DURATION;
309 PRBool doIters = PR_FALSE;
310 PRBool doTime = PR_FALSE;
311 PRBool useTokenKey = PR_FALSE; /* use PKCS#11 token
312 object key */
313 PRBool useSessionKey = PR_FALSE; /* use PKCS#11 session
314 object key */
315 PRBool useBLKey = PR_FALSE; /* use freebl */
316 PK11SlotInfo* slot = NULL; /* slot for session
317 object key operations */
318 PRBool doKeyGen = PR_FALSE;
319 int publicExponent = DEFAULT_EXPONENT;
320 PK11Keys keys;
321 int peCount = 0;
322 CK_BYTE pubEx[4];
323 SECItem pe;
324 RSAPublicKey pubKeyStr;
325 int threadNum = DEFAULT_THREADS;
326 ThreadRunData ** runDataArr = NULL;
327 PRThread ** threadsArr = NULL;
328 int calcThreads = 0;
330 progName = strrchr(argv[0], '/');
331 if (!progName)
332 progName = strrchr(argv[0], '\\');
333 progName = progName ? progName+1 : argv[0];
335 optstate = PL_CreateOptState(argc, argv, "d:ef:gh:i:k:n:p:st:w:x:");
336 while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
337 switch (optstate->option) {
338 case '?':
339 Usage(progName);
340 break;
341 case 'd':
342 secDir = PORT_Strdup(optstate->value);
343 break;
344 case 'i':
345 iters = INT_ARG(optstate->value, DEFAULT_ITERS);
346 doIters = PR_TRUE;
347 break;
348 case 's':
349 doPriv = PR_TRUE;
350 break;
351 case 'e':
352 doPub = PR_TRUE;
353 break;
354 case 'g':
355 doKeyGen = PR_TRUE;
356 break;
357 case 'n':
358 nickname = PORT_Strdup(optstate->value);
359 /* for compatibility, nickname of "none" means go to freebl */
360 if (nickname && strcmp(nickname, "none")) {
361 useTokenKey = PR_TRUE;
362 } else {
363 useBLKey = PR_TRUE;
364 }
365 break;
366 case 'p':
367 seconds = INT_ARG(optstate->value, DEFAULT_DURATION);
368 doTime = PR_TRUE;
369 break;
370 case 'h':
371 slotname = PORT_Strdup(optstate->value);
372 useSessionKey = PR_TRUE;
373 break;
374 case 'k':
375 keybits = INT_ARG(optstate->value, DEFAULT_KEY_BITS);
376 break;
377 case 'w':
378 pwData.data = PORT_Strdup(optstate->value);;
379 pwData.source = PW_PLAINTEXT;
380 break;
381 case 'f':
382 pwData.data = PORT_Strdup(optstate->value);
383 pwData.source = PW_FROMFILE;
384 break;
385 case 'x':
386 /* -x public exponent (for RSA keygen) */
387 publicExponent = INT_ARG(optstate->value, DEFAULT_EXPONENT);
388 break;
389 case 't':
390 threadNum = INT_ARG(optstate->value, DEFAULT_THREADS);
391 break;
392 }
393 }
394 if (optstatus == PL_OPT_BAD)
395 Usage(progName);
397 if ((doPriv && doPub) || (doIters && doTime) ||
398 ((useTokenKey + useSessionKey + useBLKey) != PR_TRUE) ||
399 (useTokenKey && keybits) || (useTokenKey && doKeyGen) ||
400 (keybits && (keybits<MIN_KEY_BITS || keybits>MAX_KEY_BITS))) {
401 Usage(progName);
402 }
404 if (!doPriv && !doPub) doPriv = PR_TRUE;
406 if (doIters && doTime) Usage(progName);
408 if (!doTime) {
409 doIters = PR_TRUE;
410 }
412 PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
414 PK11_SetPasswordFunc(SECU_GetModulePassword);
415 secDir = SECU_ConfigDirectory(secDir);
417 if (useTokenKey || useSessionKey) {
418 rv = NSS_Init(secDir);
419 if (rv != SECSuccess) {
420 fprintf(stderr, "NSS_Init failed.\n");
421 exit(1);
422 }
423 } else {
424 rv = NSS_NoDB_Init(NULL);
425 if (rv != SECSuccess) {
426 fprintf(stderr, "NSS_NoDB_Init failed.\n");
427 exit(1);
428 }
429 }
431 if (useTokenKey) {
432 CK_OBJECT_HANDLE kh = CK_INVALID_HANDLE;
433 CERTCertDBHandle* certdb = NULL;
434 certdb = CERT_GetDefaultCertDB();
436 cert = PK11_FindCertFromNickname(nickname, &pwData);
437 if (cert == NULL) {
438 fprintf(stderr,
439 "Can't find certificate by name \"%s\"\n", nickname);
440 exit(1);
441 }
442 pubHighKey = CERT_ExtractPublicKey(cert);
443 if (pubHighKey == NULL) {
444 fprintf(stderr, "Can't extract public key from certificate");
445 exit(1);
446 }
448 if (doPub) {
449 /* do public key ops */
450 fn = (RSAOp)PK11_PublicKeyOp;
451 rsaKey = (void *) pubHighKey;
453 kh = PK11_ImportPublicKey(cert->slot, pubHighKey, PR_FALSE);
454 if (CK_INVALID_HANDLE == kh) {
455 fprintf(stderr,
456 "Unable to import public key to certificate slot.");
457 exit(1);
458 }
459 pubHighKey->pkcs11Slot = PK11_ReferenceSlot(cert->slot);
460 pubHighKey->pkcs11ID = kh;
461 printf("Using PKCS#11 for RSA encryption with token %s.\n",
462 PK11_GetTokenName(cert->slot));
463 } else {
464 /* do private key ops */
465 privHighKey = PK11_FindKeyByAnyCert(cert, &pwData);
466 if (privHighKey == NULL) {
467 fprintf(stderr,
468 "Can't find private key by name \"%s\"\n", nickname);
469 exit(1);
470 }
472 SECKEY_CacheStaticFlags(privHighKey);
473 fn = (RSAOp)PK11_PrivateKeyOp;
474 keys.privKey = privHighKey;
475 keys.pubKey = pubHighKey;
476 rsaKey = (void *) &keys;
477 printf("Using PKCS#11 for RSA decryption with token %s.\n",
478 PK11_GetTokenName(privHighKey->pkcs11Slot));
479 }
480 } else
482 if (useSessionKey) {
483 /* use PKCS#11 session key objects */
484 PK11RSAGenParams rsaparams;
485 void * params;
487 slot = PK11_FindSlotByName(slotname); /* locate target slot */
488 if (!slot) {
489 fprintf(stderr, "Can't find slot \"%s\"\n", slotname);
490 exit(1);
491 }
493 doKeyGen = PR_TRUE; /* Always do a keygen for session keys.
494 Import of hardcoded key is not supported */
495 /* do a temporary keygen in selected slot */
496 if (!keybits) {
497 keybits = DEFAULT_KEY_BITS;
498 }
500 printf("Using PKCS#11 with %ld bits session key in token %s.\n",
501 keybits, PK11_GetTokenName(slot));
503 rsaparams.keySizeInBits = keybits;
504 rsaparams.pe = publicExponent;
505 params = &rsaparams;
507 fprintf(stderr,"\nGenerating RSA key. This may take a few moments.\n");
509 privHighKey = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN,
510 params, &pubHighKey, PR_FALSE,
511 PR_FALSE, (void*)&pwData);
512 if (!privHighKey) {
513 fprintf(stderr,
514 "Key generation failed in token \"%s\"\n",
515 PK11_GetTokenName(slot));
516 exit(1);
517 }
519 SECKEY_CacheStaticFlags(privHighKey);
521 fprintf(stderr,"Keygen completed.\n");
523 if (doPub) {
524 /* do public key operations */
525 fn = (RSAOp)PK11_PublicKeyOp;
526 rsaKey = (void *) pubHighKey;
527 } else {
528 /* do private key operations */
529 fn = (RSAOp)PK11_PrivateKeyOp;
530 keys.privKey = privHighKey;
531 keys.pubKey = pubHighKey;
532 rsaKey = (void *) &keys;
533 }
534 } else
536 {
537 /* use freebl directly */
538 if (!keybits) {
539 keybits = DEFAULT_KEY_BITS;
540 }
541 if (!doKeyGen) {
542 if (keybits != DEFAULT_KEY_BITS) {
543 doKeyGen = PR_TRUE;
544 }
545 }
546 printf("Using freebl with %ld bits key.\n", keybits);
547 if (doKeyGen) {
548 fprintf(stderr,"\nGenerating RSA key. "
549 "This may take a few moments.\n");
550 for (i=0; i < 4; i++) {
551 if (peCount || (publicExponent & ((unsigned long)0xff000000L >>
552 (i*8)))) {
553 pubEx[peCount] = (CK_BYTE)((publicExponent >>
554 (3-i)*8) & 0xff);
555 peCount++;
556 }
557 }
558 pe.len = peCount;
559 pe.data = &pubEx[0];
560 pe.type = siBuffer;
562 rsaKey = RSA_NewKey(keybits, &pe);
563 fprintf(stderr,"Keygen completed.\n");
564 } else {
565 /* use a hardcoded key */
566 printf("Using hardcoded %ld bits key.\n", keybits);
567 if (doPub) {
568 pubKey = getDefaultRSAPublicKey();
569 } else {
570 privKey = getDefaultRSAPrivateKey();
571 }
572 }
574 if (doPub) {
575 /* do public key operations */
576 fn = (RSAOp)RSA_PublicKeyOp;
577 if (rsaKey) {
578 /* convert the RSAPrivateKey to RSAPublicKey */
579 pubKeyStr.arena = NULL;
580 pubKeyStr.modulus = ((RSAPrivateKey*)rsaKey)->modulus;
581 pubKeyStr.publicExponent =
582 ((RSAPrivateKey*)rsaKey)->publicExponent;
583 rsaKey = &pubKeyStr;
584 } else {
585 /* convert NSSLOWKeyPublicKey to RSAPublicKey */
586 rsaKey = (void *)(&pubKey->u.rsa);
587 }
588 PORT_Assert(rsaKey);
589 } else {
590 /* do private key operations */
591 fn = (RSAOp)RSA_PrivateKeyOp;
592 if (privKey) {
593 /* convert NSSLOWKeyPrivateKey to RSAPrivateKey */
594 rsaKey = (void *)(&privKey->u.rsa);
595 }
596 PORT_Assert(rsaKey);
597 }
598 }
600 memset(buf, 1, sizeof buf);
601 rv = fn(rsaKey, buf2, buf);
602 if (rv != SECSuccess) {
603 PRErrorCode errNum;
604 const char * errStr = NULL;
606 errNum = PORT_GetError();
607 if (errNum)
608 errStr = SECU_Strerror(errNum);
609 else
610 errNum = rv;
611 if (!errStr)
612 errStr = "(null)";
613 fprintf(stderr, "Error in RSA operation: %d : %s\n", errNum, errStr);
614 exit(1);
615 }
617 threadsArr = (PRThread**)PORT_Alloc(threadNum*sizeof(PRThread*));
618 runDataArr = (ThreadRunData**)PORT_Alloc(threadNum*sizeof(ThreadRunData*));
619 timeCtx = CreateTimingContext();
620 TimingBegin(timeCtx, PR_Now());
621 for (i = 0;i < threadNum;i++) {
622 runDataArr[i] = (ThreadRunData*)PORT_Alloc(sizeof(ThreadRunData));
623 runDataArr[i]->fn = fn;
624 runDataArr[i]->buf = buf;
625 runDataArr[i]->doIters = &doIters;
626 runDataArr[i]->rsaKey = rsaKey;
627 runDataArr[i]->seconds = seconds;
628 runDataArr[i]->iters = iters;
629 threadsArr[i] =
630 PR_CreateThread(PR_USER_THREAD,
631 ThreadExecFunction,
632 (void*) runDataArr[i],
633 PR_PRIORITY_NORMAL,
634 PR_GLOBAL_THREAD,
635 PR_JOINABLE_THREAD,
636 0);
637 }
638 iters = 0;
639 calcThreads = 0;
640 for (i = 0;i < threadNum;i++, calcThreads++)
641 {
642 PR_JoinThread(threadsArr[i]);
643 if (runDataArr[i]->status != SECSuccess) {
644 const char * errStr = SECU_Strerror(runDataArr[i]->errNum);
645 fprintf(stderr, "Thread %d: Error in RSA operation: %d : %s\n",
646 i, runDataArr[i]->errNum, errStr);
647 calcThreads -= 1;
648 } else {
649 iters += runDataArr[i]->iterRes;
650 }
651 PORT_Free((void*)runDataArr[i]);
652 }
653 PORT_Free(runDataArr);
654 PORT_Free(threadsArr);
656 TimingEnd(timeCtx, PR_Now());
658 printf("%ld iterations in %s\n",
659 iters, TimingGenerateString(timeCtx));
660 printf("%.2f operations/s .\n", ((double)(iters)*(double)1000000.0) /
661 (double)timeCtx->interval );
662 TimingDivide(timeCtx, iters);
663 printf("one operation every %s\n", TimingGenerateString(timeCtx));
665 if (pubHighKey) {
666 SECKEY_DestroyPublicKey(pubHighKey);
667 }
669 if (privHighKey) {
670 SECKEY_DestroyPrivateKey(privHighKey);
671 }
673 if (cert) {
674 CERT_DestroyCertificate(cert);
675 }
677 if (NSS_Shutdown() != SECSuccess) {
678 exit(1);
679 }
681 return 0;
682 }