/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "seccomon.h"
#include "cert.h"
#include "secutil.h"
#include "nspr.h"
#include "nss.h"
#include "blapi.h"
#include "plgetopt.h"
#include "lowkeyi.h"
#include "pk11pub.h"
#define DEFAULT_ITERS 10
#define DEFAULT_DURATION 10
#define DEFAULT_KEY_BITS 1024
#define MIN_KEY_BITS 512
#define MAX_KEY_BITS 65536
#define BUFFER_BYTES MAX_KEY_BITS / 8
#define DEFAULT_THREADS 1
#define DEFAULT_EXPONENT 0x10001
extern NSSLOWKEYPrivateKey *getDefaultRSAPrivateKey(
int);
extern NSSLOWKEYPublicKey *getDefaultRSAPublicKey(
int);
secuPWData pwData = { PW_NONE, NULL };
typedef struct TimingContextStr TimingContext;
struct TimingContextStr {
PRTime start;
PRTime end;
PRTime interval;
long days;
int hours;
int minutes;
int seconds;
int millisecs;
};
TimingContext *
CreateTimingContext(
void)
{
return PORT_Alloc(
sizeof(TimingContext));
}
void
DestroyTimingContext(TimingContext *ctx)
{
PORT_Free(ctx);
}
void
TimingBegin(TimingContext *ctx, PRTime begin)
{
ctx->start = begin;
}
static void
timingUpdate(TimingContext *ctx)
{
PRInt64 tmp, remaining;
PRInt64 L1000, L60, L24;
LL_I2L(L1000, 1000);
LL_I2L(L60, 60);
LL_I2L(L24, 24);
LL_DIV(remaining, ctx->interval, L1000);
LL_MOD(tmp, remaining, L1000);
LL_L2I(ctx->millisecs, tmp);
LL_DIV(remaining, remaining, L1000);
LL_MOD(tmp, remaining, L60);
LL_L2I(ctx->seconds, tmp);
LL_DIV(remaining, remaining, L60);
LL_MOD(tmp, remaining, L60);
LL_L2I(ctx->minutes, tmp);
LL_DIV(remaining, remaining, L60);
LL_MOD(tmp, remaining, L24);
LL_L2I(ctx->hours, tmp);
LL_DIV(remaining, remaining, L24);
LL_L2I(ctx->days, remaining);
}
void
TimingEnd(TimingContext *ctx, PRTime end)
{
ctx->end = end;
LL_SUB(ctx->interval, ctx->end, ctx->start);
PORT_Assert(LL_GE_ZERO(ctx->interval));
timingUpdate(ctx);
}
void
TimingDivide(TimingContext *ctx,
int divisor)
{
PRInt64 tmp;
LL_I2L(tmp, divisor);
LL_DIV(ctx->interval, ctx->interval, tmp);
timingUpdate(ctx);
}
char *
TimingGenerateString(TimingContext *ctx)
{
char *buf = NULL;
if (ctx->days != 0) {
buf = PR_sprintf_append(buf,
"%d days", ctx->days);
}
if (ctx->hours != 0) {
if (buf != NULL)
buf = PR_sprintf_append(buf,
", ");
buf = PR_sprintf_append(buf,
"%d hours", ctx->hours);
}
if (ctx->minutes != 0) {
if (buf != NULL)
buf = PR_sprintf_append(buf,
", ");
buf = PR_sprintf_append(buf,
"%d minutes", ctx->minutes);
}
if (buf != NULL)
buf = PR_sprintf_append(buf,
", and ");
if (!buf && ctx->seconds == 0) {
int interval;
LL_L2I(interval, ctx->interval);
if (ctx->millisecs < 100)
buf = PR_sprintf_append(buf,
"%d microseconds", interval);
else
buf = PR_sprintf_append(buf,
"%d milliseconds", ctx->millisecs);
}
else if (ctx->millisecs == 0) {
buf = PR_sprintf_append(buf,
"%d seconds", ctx->seconds);
}
else {
buf = PR_sprintf_append(buf,
"%d.%03d seconds",
ctx->seconds, ctx->millisecs);
}
return buf;
}
void
Usage(
char *progName)
{
fprintf(stderr,
"Usage: %s [-s | -e] [-i iterations | -p period] "
"[-t threads]\n[-n none [-k keylength] [ [-g] -x exponent] |\n"
" -n token:nickname [-d certdir] [-w password] |\n"
" -h token [-d certdir] [-w password] [-g] [-k keylength] "
"[-x exponent] [-f pwfile]\n",
progName);
fprintf(stderr,
"%-20s Cert database directory (default is ~/.netscape)\n",
"-d certdir");
fprintf(stderr,
"%-20s How many operations to perform\n",
"-i iterations");
fprintf(stderr,
"%-20s How many seconds to run\n",
"-p period");
fprintf(stderr,
"%-20s Perform signing (private key) operations\n",
"-s");
fprintf(stderr,
"%-20s Perform encryption (public key) operations\n",
"-e");
fprintf(stderr,
"%-20s Nickname of certificate or key, prefixed "
"by optional token name\n",
"-n nickname");
fprintf(stderr,
"%-20s PKCS#11 token to perform operation with.\n",
"-h token");
fprintf(stderr,
"%-20s key size in bits, from %d to %d\n",
"-k keylength",
MIN_KEY_BITS, MAX_KEY_BITS);
fprintf(stderr,
"%-20s token password\n",
"-w password");
fprintf(stderr,
"%-20s temporary key generation. Not for token keys.\n",
"-g");
fprintf(stderr,
"%-20s set public exponent for keygen\n",
"-x");
fprintf(stderr,
"%-20s Number of execution threads (default 1)\n",
"-t threads");
exit(-1);
}
static void
dumpBytes(
unsigned char *b,
int l)
{
int i;
if (l <= 0)
return;
for (i = 0; i < l; ++i) {
if (i % 16 == 0)
printf(
"\t");
printf(
" %02x", b[i]);
if (i % 16 == 15)
printf(
"\n");
}
if ((i % 16) != 0)
printf(
"\n");
}
static void
dumpItem(SECItem *item,
const char *description)
{
if (item->len & 1 && item->data[0] == 0) {
printf(
"%s: (%d bytes)\n", description, item->len - 1);
dumpBytes(item->data + 1, item->len - 1);
}
else {
printf(
"%s: (%d bytes)\n", description, item->len);
dumpBytes(item->data, item->len);
}
}
void
printPrivKey(NSSLOWKEYPrivateKey *privKey)
{
RSAPrivateKey *rsa = &privKey->u.rsa;
dumpItem(&rsa->modulus,
"n");
dumpItem(&rsa->publicExponent,
"e");
dumpItem(&rsa->privateExponent,
"d");
dumpItem(&rsa->prime1,
"P");
dumpItem(&rsa->prime2,
"Q");
dumpItem(&rsa->exponent1,
"d % (P-1)");
dumpItem(&rsa->exponent2,
"d % (Q-1)");
dumpItem(&rsa->coefficient,
"(Q ** -1) % P");
puts(
"");
}
typedef SECStatus (*RSAOp)(
void *key,
unsigned char *output,
unsigned char *input);
typedef struct {
SECKEYPublicKey *pubKey;
SECKEYPrivateKey *privKey;
} PK11Keys;
SECStatus
PK11_PublicKeyOp(SECKEYPublicKey *key,
unsigned char *output,
unsigned char *input)
{
return PK11_PubEncryptRaw(key, output, input, key->u.rsa.modulus.len,
NULL);
}
SECStatus
PK11_PrivateKeyOp(PK11Keys *keys,
unsigned char *output,
unsigned char *input)
{
unsigned outLen = 0;
return PK11_PrivDecryptRaw(keys->privKey,
output, &outLen,
keys->pubKey->u.rsa.modulus.len, input,
keys->pubKey->u.rsa.modulus.len);
}
typedef struct ThreadRunDataStr ThreadRunData;
struct ThreadRunDataStr {
const PRBool *doIters;
const void *rsaKey;
const unsigned char *buf;
RSAOp fn;
int seconds;
long iters;
long iterRes;
PRErrorCode errNum;
SECStatus status;
};
void
ThreadExecFunction(
void *data)
{
ThreadRunData *tdata = (ThreadRunData *)data;
unsigned char buf2[BUFFER_BYTES];
tdata->status = SECSuccess;
if (*tdata->doIters) {
long i = tdata->iters;
tdata->iterRes = 0;
while (i--) {
SECStatus rv = tdata->fn((
void *)tdata->rsaKey, buf2,
(
unsigned char *)tdata->buf);
if (rv != SECSuccess) {
tdata->errNum = PORT_GetError();
tdata->status = rv;
break;
}
tdata->iterRes++;
}
}
else {
PRIntervalTime total = PR_SecondsToInterval(tdata->seconds);
PRIntervalTime start = PR_IntervalNow();
tdata->iterRes = 0;
while (PR_IntervalNow() - start < total) {
SECStatus rv = tdata->fn((
void *)tdata->rsaKey, buf2,
(
unsigned char *)tdata->buf);
if (rv != SECSuccess) {
tdata->errNum = PORT_GetError();
tdata->status = rv;
break;
}
tdata->iterRes++;
}
}
}
#define INT_ARG(arg, def) atol(arg) > 0 ? atol(arg) : def
int
main(
int argc,
char **argv)
{
TimingContext *timeCtx = NULL;
SECKEYPublicKey *pubHighKey = NULL;
SECKEYPrivateKey *privHighKey = NULL;
NSSLOWKEYPrivateKey *privKey = NULL;
NSSLOWKEYPublicKey *pubKey = NULL;
CERTCertificate *cert = NULL;
char *progName = NULL;
char *secDir = NULL;
char *nickname = NULL;
char *slotname = NULL;
long keybits = 0;
RSAOp fn;
void *rsaKeyPtr = NULL;
PLOptState *optstate;
PLOptStatus optstatus;
long iters = DEFAULT_ITERS;
int i;
PRBool doPriv = PR_FALSE;
PRBool doPub = PR_FALSE;
int rv;
unsigned char buf[BUFFER_BYTES];
unsigned char buf2[BUFFER_BYTES];
int seconds = DEFAULT_DURATION;
PRBool doIters = PR_FALSE;
PRBool doTime = PR_FALSE;
PRBool useTokenKey = PR_FALSE;
/* use PKCS#11 token
object key */
PRBool useSessionKey = PR_FALSE;
/* use PKCS#11 session
object key */
PRBool useBLKey = PR_FALSE;
/* use freebl */
PK11SlotInfo *slot = NULL;
/* slot for session
object key operations */
PRBool doKeyGen = PR_FALSE;
int publicExponent = DEFAULT_EXPONENT;
PK11Keys keys;
int peCount = 0;
CK_BYTE pubEx[4];
SECItem pe;
RSAPublicKey pubKeyStr;
int threadNum = DEFAULT_THREADS;
ThreadRunData **runDataArr = NULL;
PRThread **threadsArr = NULL;
int calcThreads = 0;
progName = strrchr(argv[0],
'/');
if (!progName)
progName = strrchr(argv[0],
'\\');
progName = progName ? progName + 1 : argv[0];
optstate = PL_CreateOptState(argc, argv,
"d:ef:gh:i:k:n:p:st:w:x:");
while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
switch (optstate->option) {
case '?':
Usage(progName);
break;
case 'd':
secDir = PORT_Strdup(optstate->value);
break;
case 'i':
iters = INT_ARG(optstate->value, DEFAULT_ITERS);
doIters = PR_TRUE;
break;
case 's':
doPriv = PR_TRUE;
break;
case 'e':
doPub = PR_TRUE;
break;
case 'g':
doKeyGen = PR_TRUE;
break;
case 'n':
nickname = PORT_Strdup(optstate->value);
/* for compatibility, nickname of "none" means go to freebl */
if (nickname && strcmp(nickname,
"none")) {
useTokenKey = PR_TRUE;
}
else {
useBLKey = PR_TRUE;
}
break;
case 'p':
seconds = INT_ARG(optstate->value, DEFAULT_DURATION);
doTime = PR_TRUE;
break;
case 'h':
slotname = PORT_Strdup(optstate->value);
useSessionKey = PR_TRUE;
break;
case 'k':
keybits = INT_ARG(optstate->value, DEFAULT_KEY_BITS);
break;
case 'w':
pwData.data = PORT_Strdup(optstate->value);
;
pwData.source = PW_PLAINTEXT;
break;
case 'f':
pwData.data = PORT_Strdup(optstate->value);
pwData.source = PW_FROMFILE;
break;
case 'x':
/* -x public exponent (for RSA keygen) */
publicExponent = INT_ARG(optstate->value, DEFAULT_EXPONENT);
break;
case 't':
threadNum = INT_ARG(optstate->value, DEFAULT_THREADS);
break;
}
}
if (optstatus == PL_OPT_BAD)
Usage(progName);
if ((doPriv && doPub) || (doIters && doTime) ||
((useTokenKey + useSessionKey + useBLKey) != PR_TRUE) ||
(useTokenKey && keybits) || (useTokenKey && doKeyGen) ||
(keybits && (keybits < MIN_KEY_BITS || keybits > MAX_KEY_BITS))) {
Usage(progName);
}
if (doIters && doTime)
Usage(progName);
if (!doTime) {
doIters = PR_TRUE;
}
PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
PK11_SetPasswordFunc(SECU_GetModulePassword);
secDir = SECU_ConfigDirectory(secDir);
if (useTokenKey || useSessionKey) {
rv = NSS_Init(secDir);
if (rv != SECSuccess) {
fprintf(stderr,
"NSS_Init failed.\n");
exit(1);
}
}
else {
rv = NSS_NoDB_Init(NULL);
if (rv != SECSuccess) {
fprintf(stderr,
"NSS_NoDB_Init failed.\n");
exit(1);
}
}
if (useTokenKey) {
CK_OBJECT_HANDLE kh = CK_INVALID_HANDLE;
cert = PK11_FindCertFromNickname(nickname, &pwData);
if (cert == NULL) {
fprintf(stderr,
"Can't find certificate by name \"%s\
"\n", nickname);
exit(1);
}
pubHighKey = CERT_ExtractPublicKey(cert);
if (pubHighKey == NULL) {
fprintf(stderr,
"Can't extract public key from certificate");
exit(1);
}
if (doPub) {
/* do public key ops */
fn = (RSAOp)PK11_PublicKeyOp;
rsaKeyPtr = (
void *)pubHighKey;
kh = PK11_ImportPublicKey(cert->slot, pubHighKey, PR_FALSE);
if (CK_INVALID_HANDLE == kh) {
fprintf(stderr,
"Unable to import public key to certificate slot.");
exit(1);
}
pubHighKey->pkcs11Slot = PK11_ReferenceSlot(cert->slot);
pubHighKey->pkcs11ID = kh;
printf(
"Using PKCS#11 for RSA encryption with token %s.\n",
PK11_GetTokenName(cert->slot));
}
else {
/* do private key ops */
privHighKey = PK11_FindKeyByAnyCert(cert, &pwData);
if (privHighKey == NULL) {
fprintf(stderr,
"Can't find private key by name \"%s\
"\n", nickname);
exit(1);
}
SECKEY_CacheStaticFlags(privHighKey);
fn = (RSAOp)PK11_PrivateKeyOp;
keys.privKey = privHighKey;
keys.pubKey = pubHighKey;
rsaKeyPtr = (
void *)&keys;
printf(
"Using PKCS#11 for RSA decryption with token %s.\n",
PK11_GetTokenName(privHighKey->pkcs11Slot));
}
}
else
if (useSessionKey) {
/* use PKCS#11 session key objects */
PK11RSAGenParams rsaparams;
void *params;
slot = PK11_FindSlotByName(slotname);
/* locate target slot */
if (!slot) {
fprintf(stderr,
"Can't find slot \"%s\
"\n", slotname);
exit(1);
}
/* do a temporary keygen in selected slot */
if (!keybits) {
keybits = DEFAULT_KEY_BITS;
}
printf(
"Using PKCS#11 with %ld bits session key in token %s.\n",
keybits, PK11_GetTokenName(slot));
rsaparams.keySizeInBits = keybits;
rsaparams.pe = publicExponent;
params = &rsaparams;
fprintf(stderr,
"\nGenerating RSA key. This may take a few moments.\n");
privHighKey = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN,
params, &pubHighKey, PR_FALSE,
PR_FALSE, (
void *)&pwData);
if (!privHighKey) {
fprintf(stderr,
"Key generation failed in token \"%s\
"\n",
PK11_GetTokenName(slot));
exit(1);
}
SECKEY_CacheStaticFlags(privHighKey);
fprintf(stderr,
"Keygen completed.\n");
if (doPub) {
/* do public key operations */
fn = (RSAOp)PK11_PublicKeyOp;
rsaKeyPtr = (
void *)pubHighKey;
}
else {
/* do private key operations */
fn = (RSAOp)PK11_PrivateKeyOp;
keys.privKey = privHighKey;
keys.pubKey = pubHighKey;
rsaKeyPtr = (
void *)&keys;
}
}
else
{
/* use freebl directly */
if (!keybits) {
keybits = DEFAULT_KEY_BITS;
}
if (!doKeyGen) {
if (keybits != DEFAULT_KEY_BITS) {
doKeyGen = PR_TRUE;
}
}
printf(
"Using freebl with %ld bits key.\n", keybits);
if (doKeyGen) {
fprintf(stderr,
"\nGenerating RSA key. "
"This may take a few moments.\n");
for (i = 0; i < 4; i++) {
if (peCount || (publicExponent & ((
unsigned long)0xff000000L >>
(i * 8)))) {
pubEx[peCount] = (CK_BYTE)((publicExponent >>
(3 - i) * 8) &
0xff);
peCount++;
}
}
pe.len = peCount;
pe.data = &pubEx[0];
pe.type = siBuffer;
rsaKeyPtr = RSA_NewKey(keybits, &pe);
fprintf(stderr,
"Keygen completed.\n");
}
else {
/* use a hardcoded key */
printf(
"Using hardcoded %ld bits key.\n", keybits);
if (doPub) {
pubKey = getDefaultRSAPublicKey(keybits);
}
else {
privKey = getDefaultRSAPrivateKey(keybits);
}
}
if (doPub) {
/* do public key operations */
fn = (RSAOp)RSA_PublicKeyOp;
if (rsaKeyPtr) {
/* convert the RSAPrivateKey to RSAPublicKey */
pubKeyStr.arena = NULL;
pubKeyStr.modulus = ((RSAPrivateKey *)rsaKeyPtr)->modulus;
pubKeyStr.publicExponent =
((RSAPrivateKey *)rsaKeyPtr)->publicExponent;
rsaKeyPtr = &pubKeyStr;
}
else {
/* convert NSSLOWKeyPublicKey to RSAPublicKey */
rsaKeyPtr = (
void *)(&pubKey->u.rsa);
}
PORT_Assert(rsaKeyPtr);
}
else {
/* do private key operations */
fn = (RSAOp)RSA_PrivateKeyOp;
if (privKey) {
/* convert NSSLOWKeyPrivateKey to RSAPrivateKey */
rsaKeyPtr = (
void *)(&privKey->u.rsa);
}
PORT_Assert(rsaKeyPtr);
}
}
memset(buf, 1,
sizeof buf);
rv = fn(rsaKeyPtr, buf2, buf);
if (rv != SECSuccess) {
PRErrorCode errNum;
const char *errStr = NULL;
errNum = PORT_GetError();
if (errNum)
errStr = SECU_Strerror(errNum);
else
errNum = rv;
if (!errStr)
errStr =
"(null)";
fprintf(stderr,
"Error in RSA operation: %d : %s\n", errNum, errStr);
exit(1);
}
threadsArr = (PRThread **)PORT_Alloc(threadNum *
sizeof(PRThread *));
runDataArr = (ThreadRunData **)PORT_Alloc(threadNum *
sizeof(ThreadRunData *));
timeCtx = CreateTimingContext();
TimingBegin(timeCtx, PR_Now());
for (i = 0; i < threadNum; i++) {
runDataArr[i] = (ThreadRunData *)PORT_Alloc(
sizeof(ThreadRunData));
runDataArr[i]->fn = fn;
runDataArr[i]->buf = buf;
runDataArr[i]->doIters = &doIters;
runDataArr[i]->rsaKey = rsaKeyPtr;
runDataArr[i]->seconds = seconds;
runDataArr[i]->iters = iters;
threadsArr[i] =
PR_CreateThread(PR_USER_THREAD,
ThreadExecFunction,
(
void *)runDataArr[i],
PR_PRIORITY_NORMAL,
PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD,
0);
}
iters = 0;
calcThreads = 0;
for (i = 0; i < threadNum; i++, calcThreads++) {
PR_JoinThread(threadsArr[i]);
if (runDataArr[i]->status != SECSuccess) {
const char *errStr = SECU_Strerror(runDataArr[i]->errNum);
fprintf(stderr,
"Thread %d: Error in RSA operation: %d : %s\n",
i, runDataArr[i]->errNum, errStr);
calcThreads -= 1;
}
else {
iters += runDataArr[i]->iterRes;
}
PORT_Free((
void *)runDataArr[i]);
}
PORT_Free(runDataArr);
PORT_Free(threadsArr);
TimingEnd(timeCtx, PR_Now());
printf(
"%ld iterations in %s\n",
iters, TimingGenerateString(timeCtx));
printf(
"%.2f operations/s .\n", ((
double)(iters) * (
double)1000000.0) / (
double)timeC
tx->interval);
TimingDivide(timeCtx, iters);
printf("one operation every %s\n", TimingGenerateString(timeCtx));
if (pubHighKey) {
SECKEY_DestroyPublicKey(pubHighKey);
}
if (privHighKey) {
SECKEY_DestroyPrivateKey(privHighKey);
}
if (cert) {
CERT_DestroyCertificate(cert);
}
if (NSS_Shutdown() != SECSuccess) {
exit(1);
}
return 0;
}