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