/* 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/. */ /* ** secutil.c - various functions used by security stuff ** */ #include "prtypes.h" #include "prtime.h" #include "prlong.h" #include "prerror.h" #include "prprf.h" #include "plgetopt.h" #include "prenv.h" #include "prnetdb.h" #include "basicutil.h" #include #include #include #ifdef XP_UNIX #include #endif #include "secoid.h" extern long DER_GetInteger(const SECItem *src); static PRBool wrapEnabled = PR_TRUE; void SECU_EnableWrap(PRBool enable) { wrapEnabled = enable; } PRBool SECU_GetWrapEnabled(void) { return wrapEnabled; } void SECU_PrintErrMsg(FILE *out, int level, const char *progName, const char *msg, ...) { va_list args; PRErrorCode err = PORT_GetError(); const char *errString = PORT_ErrorToString(err); va_start(args, msg); SECU_Indent(out, level); fprintf(out, "%s: ", progName); vfprintf(out, msg, args); if (errString != NULL && PORT_Strlen(errString) > 0) fprintf(out, ": %s\n", errString); else fprintf(out, ": error %d\n", (int)err); va_end(args); } void SECU_PrintError(const char *progName, const char *msg, ...) { va_list args; PRErrorCode err = PORT_GetError(); const char *errName = PR_ErrorToName(err); const char *errString = PR_ErrorToString(err, 0); va_start(args, msg); fprintf(stderr, "%s: ", progName); vfprintf(stderr, msg, args); if (errName != NULL) { fprintf(stderr, ": %s", errName); } else { fprintf(stderr, ": error %d", (int)err); } if (errString != NULL && PORT_Strlen(errString) > 0) fprintf(stderr, ": %s\n", errString); va_end(args); } void SECU_PrintSystemError(const char *progName, const char *msg, ...) { va_list args; va_start(args, msg); fprintf(stderr, "%s: ", progName); vfprintf(stderr, msg, args); fprintf(stderr, ": %s\n", strerror(errno)); va_end(args); } SECStatus secu_StdinToItem(SECItem *dst) { unsigned char buf[1000]; PRInt32 numBytes; PRBool notDone = PR_TRUE; dst->len = 0; dst->data = NULL; while (notDone) { numBytes = PR_Read(PR_STDIN, buf, sizeof(buf)); if (numBytes < 0) { return SECFailure; } if (numBytes == 0) break; if (dst->data) { unsigned char *p = dst->data; dst->data = (unsigned char *)PORT_Realloc(p, dst->len + numBytes); if (!dst->data) { PORT_Free(p); } } else { dst->data = (unsigned char *)PORT_Alloc(numBytes); } if (!dst->data) { return SECFailure; } PORT_Memcpy(dst->data + dst->len, buf, numBytes); dst->len += numBytes; } return SECSuccess; } SECStatus SECU_FileToItem(SECItem *dst, PRFileDesc *src) { PRFileInfo info; PRInt32 numBytes; PRStatus prStatus; if (src == PR_STDIN) return secu_StdinToItem(dst); prStatus = PR_GetOpenFileInfo(src, &info); if (prStatus != PR_SUCCESS) { PORT_SetError(SEC_ERROR_IO); return SECFailure; } /* XXX workaround for 3.1, not all utils zero dst before sending */ dst->data = 0; if (!SECITEM_AllocItem(NULL, dst, info.size)) goto loser; numBytes = PR_Read(src, dst->data, info.size); if (numBytes != info.size) { PORT_SetError(SEC_ERROR_IO); goto loser; } return SECSuccess; loser: SECITEM_FreeItem(dst, PR_FALSE); dst->data = NULL; return SECFailure; } SECStatus SECU_TextFileToItem(SECItem *dst, PRFileDesc *src) { PRFileInfo info; PRInt32 numBytes; PRStatus prStatus; unsigned char *buf; if (src == PR_STDIN) return secu_StdinToItem(dst); prStatus = PR_GetOpenFileInfo(src, &info); if (prStatus != PR_SUCCESS) { PORT_SetError(SEC_ERROR_IO); return SECFailure; } buf = (unsigned char *)PORT_Alloc(info.size); if (!buf) return SECFailure; numBytes = PR_Read(src, buf, info.size); if (numBytes != info.size) { PORT_SetError(SEC_ERROR_IO); goto loser; } if (buf[numBytes - 1] == '\n') numBytes--; #ifdef _WINDOWS if (buf[numBytes - 1] == '\r') numBytes--; #endif /* XXX workaround for 3.1, not all utils zero dst before sending */ dst->data = 0; if (!SECITEM_AllocItem(NULL, dst, numBytes)) goto loser; memcpy(dst->data, buf, numBytes); PORT_Free(buf); return SECSuccess; loser: PORT_Free(buf); return SECFailure; } #define INDENT_MULT 4 void SECU_Indent(FILE *out, int level) { int i; for (i = 0; i < level; i++) { fprintf(out, " "); } } void SECU_Newline(FILE *out) { fprintf(out, "\n"); } void SECU_PrintAsHex(FILE *out, const SECItem *data, const char *m, int level) { unsigned i; int column = 0; PRBool isString = PR_TRUE; PRBool isWhiteSpace = PR_TRUE; PRBool printedHex = PR_FALSE; unsigned int limit = 15; if (m) { SECU_Indent(out, level); fprintf(out, "%s:", m); level++; if (wrapEnabled) fprintf(out, "\n"); } if (wrapEnabled) { SECU_Indent(out, level); column = level * INDENT_MULT; } if (!data->len) { fprintf(out, "(empty)\n"); return; } /* take a pass to see if it's all printable. */ for (i = 0; i < data->len; i++) { unsigned char val = data->data[i]; if (!val || !isprint(val)) { isString = PR_FALSE; break; } if (isWhiteSpace && !isspace(val)) { isWhiteSpace = PR_FALSE; } } /* Short values, such as bit strings (which are printed with this ** function) often look like strings, but we want to see the bits. ** so this test assures that short values will be printed in hex, ** perhaps in addition to being printed as strings. ** The threshold size (4 bytes) is arbitrary. */ if (!isString || data->len <= 4) { for (i = 0; i < data->len; i++) { if (i != data->len - 1) { fprintf(out, "%02x:", data->data[i]); column += 3; } else { fprintf(out, "%02x", data->data[i]); column += 2; break; } if (wrapEnabled && (column > 76 || (i % 16 == limit))) { SECU_Newline(out); SECU_Indent(out, level); column = level * INDENT_MULT; limit = i % 16; } } printedHex = PR_TRUE; } if (isString && !isWhiteSpace) { if (printedHex != PR_FALSE) { SECU_Newline(out); SECU_Indent(out, level); column = level * INDENT_MULT; } for (i = 0; i < data->len; i++) { unsigned char val = data->data[i]; if (val) { fprintf(out, "%c", val); column++; } else { column = 77; } if (wrapEnabled && column > 76) { SECU_Newline(out); SECU_Indent(out, level); column = level * INDENT_MULT; } } } if (column != level * INDENT_MULT) { SECU_Newline(out); } } const char *hex = "0123456789abcdef"; const char printable[257] = { "................" /* 0x */ "................" /* 1x */ " !\"#$%&'()*+,-./" /* 2x */ "0123456789:;<=>?" /* 3x */ "@ABCDEFGHIJKLMNO" /* 4x */ "PQRSTUVWXYZ[\\]^_" /* 5x */ "`abcdefghijklmno" /* 6x */ "pqrstuvwxyz{|}~." /* 7x */ "................" /* 8x */ "................" /* 9x */ "................" /* ax */ "................" /* bx */ "................" /* cx */ "................" /* dx */ "................" /* ex */ "................" /* fx */ }; void SECU_PrintBuf(FILE *out, const char *msg, const void *vp, int len) { const unsigned char *cp = (const unsigned char *)vp; char buf[80]; char *bp; char *ap; fprintf(out, "%s [Len: %d]\n", msg, len); memset(buf, ' ', sizeof buf); bp = buf; ap = buf + 50; while (--len >= 0) { unsigned char ch = *cp++; *bp++ = hex[(ch >> 4) & 0xf]; *bp++ = hex[ch & 0xf]; *bp++ = ' '; *ap++ = printable[ch]; if (ap - buf >= 66) { *ap = 0; fprintf(out, " %s\n", buf); memset(buf, ' ', sizeof buf); bp = buf; ap = buf + 50; } } if (bp > buf) { *ap = 0; fprintf(out, " %s\n", buf); } } /* This expents i->data[0] to be the MSB of the integer. ** if you want to print a DER-encoded integer (with the tag and length) ** call SECU_PrintEncodedInteger(); */ void SECU_PrintInteger(FILE *out, const SECItem *i, const char *m, int level) { int iv; if (!i || !i->len || !i->data) { SECU_Indent(out, level); if (m) { fprintf(out, "%s: (null)\n", m); } else { fprintf(out, "(null)\n"); } } else if (i->len > 4) { SECU_PrintAsHex(out, i, m, level); } else { if (i->type == siUnsignedInteger && *i->data & 0x80) { /* Make sure i->data has zero in the highest bite * if i->data is an unsigned integer */ SECItem tmpI; char data[] = { 0, 0, 0, 0, 0 }; PORT_Memcpy(data + 1, i->data, i->len); tmpI.len = i->len + 1; tmpI.data = (void *)data; iv = DER_GetInteger(&tmpI); } else { iv = DER_GetInteger(i); } SECU_Indent(out, level); if (m) { fprintf(out, "%s: %d (0x%x)\n", m, iv, iv); } else { fprintf(out, "%d (0x%x)\n", iv, iv); } } } #if defined(DEBUG) || defined(FORCE_PR_ASSERT) /* Returns true iff a[i].flag has a duplicate in a[i+1 : count-1] */ static PRBool HasShortDuplicate(int i, secuCommandFlag *a, int count) { char target = a[i].flag; int j; /* duplicate '\0' flags are okay, they are used with long forms */ for (j = i + 1; j < count; j++) { if (a[j].flag && a[j].flag == target) { return PR_TRUE; } } return PR_FALSE; } /* Returns true iff a[i].longform has a duplicate in a[i+1 : count-1] */ static PRBool HasLongDuplicate(int i, secuCommandFlag *a, int count) { int j; char *target = a[i].longform; if (!target) return PR_FALSE; for (j = i + 1; j < count; j++) { if (a[j].longform && strcmp(a[j].longform, target) == 0) { return PR_TRUE; } } return PR_FALSE; } /* Returns true iff a has no short or long form duplicates */ PRBool HasNoDuplicates(secuCommandFlag *a, int count) { int i; for (i = 0; i < count; i++) { if (a[i].flag && HasShortDuplicate(i, a, count)) { return PR_FALSE; } if (a[i].longform && HasLongDuplicate(i, a, count)) { return PR_FALSE; } } return PR_TRUE; } #endif SECStatus SECU_ParseCommandLine(int argc, char **argv, char *progName, const secuCommand *cmd) { PRBool found; PLOptState *optstate; PLOptStatus status; char *optstring; PLLongOpt *longopts = NULL; int i, j; int lcmd = 0, lopt = 0; PR_ASSERT(HasNoDuplicates(cmd->commands, cmd->numCommands)); PR_ASSERT(HasNoDuplicates(cmd->options, cmd->numOptions)); optstring = (char *)PORT_Alloc(cmd->numCommands + 2 * cmd->numOptions + 1); if (optstring == NULL) return SECFailure; j = 0; for (i = 0; i < cmd->numCommands; i++) { if (cmd->commands[i].flag) /* single character option ? */ optstring[j++] = cmd->commands[i].flag; if (cmd->commands[i].longform) lcmd++; } for (i = 0; i < cmd->numOptions; i++) { if (cmd->options[i].flag) { optstring[j++] = cmd->options[i].flag; if (cmd->options[i].needsArg) optstring[j++] = ':'; } if (cmd->options[i].longform) lopt++; } optstring[j] = '\0'; if (lcmd + lopt > 0) { longopts = PORT_NewArray(PLLongOpt, lcmd + lopt + 1); if (!longopts) { PORT_Free(optstring); return SECFailure; } j = 0; for (i = 0; j < lcmd && i < cmd->numCommands; i++) { if (cmd->commands[i].longform) { longopts[j].longOptName = cmd->commands[i].longform; longopts[j].longOption = 0; longopts[j++].valueRequired = cmd->commands[i].needsArg; } } lopt += lcmd; for (i = 0; j < lopt && i < cmd->numOptions; i++) { if (cmd->options[i].longform) { longopts[j].longOptName = cmd->options[i].longform; longopts[j].longOption = 0; longopts[j++].valueRequired = cmd->options[i].needsArg; } } longopts[j].longOptName = NULL; } optstate = PL_CreateLongOptState(argc, argv, optstring, longopts); if (!optstate) { PORT_Free(optstring); PORT_Free(longopts); return SECFailure; } /* Parse command line arguments */ while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { const char *optstatelong; char option = optstate->option; /* positional parameter, single-char option or long opt? */ if (optstate->longOptIndex == -1) { /* not a long opt */ if (option == '\0') continue; /* it's a positional parameter */ optstatelong = ""; } else { /* long opt */ if (option == '\0') option = '\377'; /* force unequal with all flags */ optstatelong = longopts[optstate->longOptIndex].longOptName; } found = PR_FALSE; for (i = 0; i < cmd->numCommands; i++) { if (cmd->commands[i].flag == option || cmd->commands[i].longform == optstatelong) { cmd->commands[i].activated = PR_TRUE; if (optstate->value) { cmd->commands[i].arg = (char *)optstate->value; } found = PR_TRUE; break; } } if (found) continue; for (i = 0; i < cmd->numOptions; i++) { if (cmd->options[i].flag == option || cmd->options[i].longform == optstatelong) { cmd->options[i].activated = PR_TRUE; if (optstate->value) { cmd->options[i].arg = (char *)optstate->value; } else if (cmd->options[i].needsArg) { status = PL_OPT_BAD; goto loser; } found = PR_TRUE; break; } } if (!found) { status = PL_OPT_BAD; break; } } loser: PL_DestroyOptState(optstate); PORT_Free(optstring); if (longopts) PORT_Free(longopts); if (status == PL_OPT_BAD) return SECFailure; return SECSuccess; } char * SECU_GetOptionArg(const secuCommand *cmd, int optionNum) { if (optionNum < 0 || optionNum >= cmd->numOptions) return NULL; if (cmd->options[optionNum].activated) return PL_strdup(cmd->options[optionNum].arg); else return NULL; } void SECU_PrintPRandOSError(const char *progName) { char buffer[513]; PRInt32 errLen = PR_GetErrorTextLength(); if (errLen > 0 && errLen < sizeof buffer) { PR_GetErrorText(buffer); } SECU_PrintError(progName, "function failed"); if (errLen > 0 && errLen < sizeof buffer) { PR_fprintf(PR_STDERR, "\t%s\n", buffer); } } SECOidTag SECU_StringToSignatureAlgTag(const char *alg) { SECOidTag hashAlgTag = SEC_OID_UNKNOWN; if (alg) { if (!PL_strcmp(alg, "MD2")) { hashAlgTag = SEC_OID_MD2; } else if (!PL_strcmp(alg, "MD4")) { hashAlgTag = SEC_OID_MD4; } else if (!PL_strcmp(alg, "MD5")) { hashAlgTag = SEC_OID_MD5; } else if (!PL_strcmp(alg, "SHA1")) { hashAlgTag = SEC_OID_SHA1; } else if (!PL_strcmp(alg, "SHA224")) { hashAlgTag = SEC_OID_SHA224; } else if (!PL_strcmp(alg, "SHA256")) { hashAlgTag = SEC_OID_SHA256; } else if (!PL_strcmp(alg, "SHA384")) { hashAlgTag = SEC_OID_SHA384; } else if (!PL_strcmp(alg, "SHA512")) { hashAlgTag = SEC_OID_SHA512; } } return hashAlgTag; } /* Caller ensures that dst is at least item->len*2+1 bytes long */ void SECU_SECItemToHex(const SECItem *item, char *dst) { if (dst && item && item->data) { unsigned char *src = item->data; unsigned int len = item->len; for (; len > 0; --len, dst += 2) { sprintf(dst, "%02x", *src++); } *dst = '\0'; } } static unsigned char nibble(char c) { c = PORT_Tolower(c); return (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1; } SECStatus SECU_SECItemHexStringToBinary(SECItem *srcdest) { unsigned int i; if (!srcdest) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (srcdest->len < 4 || (srcdest->len % 2)) { /* too short to convert, or even number of characters */ PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } if (PORT_Strncasecmp((const char *)srcdest->data, "0x", 2)) { /* wrong prefix */ PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } /* 1st pass to check for hex characters */ for (i = 2; i < srcdest->len; i++) { char c = PORT_Tolower(srcdest->data[i]); if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) { PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } } /* 2nd pass to convert */ for (i = 2; i < srcdest->len; i += 2) { srcdest->data[(i - 2) / 2] = (nibble(srcdest->data[i]) << 4) + nibble(srcdest->data[i + 1]); } /* adjust length */ srcdest->len -= 2; srcdest->len /= 2; return SECSuccess; } SECItem * SECU_HexString2SECItem(PLArenaPool *arena, SECItem *item, const char *str) { int i = 0; int byteval = 0; int tmp = PORT_Strlen(str); PORT_Assert(arena); PORT_Assert(item); if ((tmp % 2) != 0) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return NULL; } item = SECITEM_AllocItem(arena, item, tmp / 2); if (item == NULL) { return NULL; } while (str[i]) { if ((str[i] >= '0') && (str[i] <= '9')) { tmp = str[i] - '0'; } else if ((str[i] >= 'a') && (str[i] <= 'f')) { tmp = str[i] - 'a' + 10; } else if ((str[i] >= 'A') && (str[i] <= 'F')) { tmp = str[i] - 'A' + 10; } else { /* item is in arena and gets freed by the caller */ return NULL; } byteval = byteval * 16 + tmp; if ((i % 2) != 0) { item->data[i / 2] = byteval; byteval = 0; } i++; } return item; } /* mapping between ECCurveName enum and SECOidTags */ static SECOidTag ecCurve_oid_map[] = { SEC_OID_UNKNOWN, /* ECCurve_noName */ SEC_OID_ANSIX962_EC_PRIME192V1, /* ECCurve_NIST_P192 */ SEC_OID_SECG_EC_SECP224R1, /* ECCurve_NIST_P224 */ SEC_OID_ANSIX962_EC_PRIME256V1, /* ECCurve_NIST_P256 */ SEC_OID_SECG_EC_SECP384R1, /* ECCurve_NIST_P384 */ SEC_OID_SECG_EC_SECP521R1, /* ECCurve_NIST_P521 */ SEC_OID_SECG_EC_SECT163K1, /* ECCurve_NIST_K163 */ SEC_OID_SECG_EC_SECT163R1, /* ECCurve_NIST_B163 */ SEC_OID_SECG_EC_SECT233K1, /* ECCurve_NIST_K233 */ SEC_OID_SECG_EC_SECT233R1, /* ECCurve_NIST_B233 */ SEC_OID_SECG_EC_SECT283K1, /* ECCurve_NIST_K283 */ SEC_OID_SECG_EC_SECT283R1, /* ECCurve_NIST_B283 */ SEC_OID_SECG_EC_SECT409K1, /* ECCurve_NIST_K409 */ SEC_OID_SECG_EC_SECT409R1, /* ECCurve_NIST_B409 */ SEC_OID_SECG_EC_SECT571K1, /* ECCurve_NIST_K571 */ SEC_OID_SECG_EC_SECT571R1, /* ECCurve_NIST_B571 */ SEC_OID_ANSIX962_EC_PRIME192V2, SEC_OID_ANSIX962_EC_PRIME192V3, SEC_OID_ANSIX962_EC_PRIME239V1, SEC_OID_ANSIX962_EC_PRIME239V2, SEC_OID_ANSIX962_EC_PRIME239V3, SEC_OID_ANSIX962_EC_C2PNB163V1, SEC_OID_ANSIX962_EC_C2PNB163V2, SEC_OID_ANSIX962_EC_C2PNB163V3, SEC_OID_ANSIX962_EC_C2PNB176V1, SEC_OID_ANSIX962_EC_C2TNB191V1, SEC_OID_ANSIX962_EC_C2TNB191V2, SEC_OID_ANSIX962_EC_C2TNB191V3, SEC_OID_ANSIX962_EC_C2PNB208W1, SEC_OID_ANSIX962_EC_C2TNB239V1, SEC_OID_ANSIX962_EC_C2TNB239V2, SEC_OID_ANSIX962_EC_C2TNB239V3, SEC_OID_ANSIX962_EC_C2PNB272W1, SEC_OID_ANSIX962_EC_C2PNB304W1, SEC_OID_ANSIX962_EC_C2TNB359V1, SEC_OID_ANSIX962_EC_C2PNB368W1, SEC_OID_ANSIX962_EC_C2TNB431R1, SEC_OID_SECG_EC_SECP112R1, SEC_OID_SECG_EC_SECP112R2, SEC_OID_SECG_EC_SECP128R1, SEC_OID_SECG_EC_SECP128R2, SEC_OID_SECG_EC_SECP160K1, SEC_OID_SECG_EC_SECP160R1, SEC_OID_SECG_EC_SECP160R2, SEC_OID_SECG_EC_SECP192K1, SEC_OID_SECG_EC_SECP224K1, SEC_OID_SECG_EC_SECP256K1, SEC_OID_SECG_EC_SECT113R1, SEC_OID_SECG_EC_SECT113R2, SEC_OID_SECG_EC_SECT131R1, SEC_OID_SECG_EC_SECT131R2, SEC_OID_SECG_EC_SECT163R1, SEC_OID_SECG_EC_SECT193R1, SEC_OID_SECG_EC_SECT193R2, SEC_OID_SECG_EC_SECT239K1, SEC_OID_UNKNOWN, /* ECCurve_WTLS_1 */ SEC_OID_UNKNOWN, /* ECCurve_WTLS_8 */ SEC_OID_UNKNOWN, /* ECCurve_WTLS_9 */ SEC_OID_CURVE25519, SEC_OID_UNKNOWN /* ECCurve_pastLastCurve */ }; SECStatus SECU_ecName2params(ECCurveName curve, SECItem *params) { SECOidData *oidData = NULL; if ((curve < ECCurve_noName) || (curve > ECCurve_pastLastCurve) || ((oidData = SECOID_FindOIDByTag(ecCurve_oid_map[curve])) == NULL)) { PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE); return SECFailure; } if (SECITEM_AllocItem(NULL, params, (2 + oidData->oid.len)) == NULL) { return SECFailure; } /* * params->data needs to contain the ASN encoding of an object ID (OID) * representing the named curve. The actual OID is in * oidData->oid.data so we simply prepend 0x06 and OID length */ params->data[0] = SEC_ASN1_OBJECT_ID; params->data[1] = oidData->oid.len; memcpy(params->data + 2, oidData->oid.data, oidData->oid.len); return SECSuccess; }