Utilities for nss samples

NSS Sample Code 0: Utilities.

These utility functions are adapted from those found in the sectool library used by the NSS security tools and other NSS test applications.

It shows the following:

  • Read DER from a file.

  • Compile file size.

  • Get seed From a noise gile.

  • Generate random numbers.

  • Get a module password.

  • Extract the password from a text file.

  • Print data as hexadecimal.

util.h

/* 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 https://mozilla.org/MPL/2.0/. */

#ifndef _UTIL_H
#define    _UTIL_H

#include <prlog.h>
#include <termios.h>
#include <base64.h>
#include <unistd.h>
#include <sys/stat.h>
#include "util.h"
#include <prprf.h>
#include <prerror.h>
#include <nss.h>
#include <pk11func.h>

/*
* These utility functions are adapted from those found in
* the sectool library used by the NSS security tools and
* other NSS test applications.
*/

typedef struct {
    enum {
        PW_NONE = 0,        /* no password */
        PW_FROMFILE = 1,    /* password stored in a file */
        PW_PLAINTEXT = 2    /* plain-text password passed in  buffer */
        /* PW_EXTERNAL = 3  */
    } source;
    char *data;
    /* depending on source this can be the actual
     * password or the file to read it from
     */
} secuPWData;

/*
 * PrintAsAscii
 */
extern void
PrintAsAscii(PRFileDesc* out, const unsigned char *data, unsigned int len);

/*
 * PrintAsHex
 */
extern void
PrintAsHex(PRFileDesc* out, const unsigned char *data, unsigned int len);

/*
 * GetDigit
 */
extern int
GetDigit(char c);

/*
 * HexToBuf
 */
extern int
HexToBuf(unsigned char *inString, SECItem *outbuf, PRBool isHexData);

/*
 * FileToItem
 */
extern SECStatus
FileToItem(SECItem *dst, PRFileDesc *src);

/*
 * CheckPassword
 */
extern PRBool
CheckPassword(char *cp);

/*
 * GetPassword
 */
extern char *
GetPassword(FILE   *input,
            FILE   *output,
            char   *prompt,
            PRBool (*ok)(char *));

/*
 * FilePasswd extracts the password from a text file
 *
 * Storing passwords is often used with server environments
 * where prompting the user for a password or requiring it
 * to be entered in the commnd line is not a feasible option.
 *
 * This function supports password extraction from files with
 * multipe passwords, one for each token. In the single password
 * case a line would just have the passord whereas in the multi-
 * password variant they could be of the form
 *
 * token_1_name:its_password
 * token_2_name:its_password
 *
 */
extern char *
FilePasswd(PK11SlotInfo *
           slot, PRBool retry, void *arg);

/*
 * GetModulePassword
 */
extern char *
GetModulePassword(PK11SlotInfo *slot,
                  int          retry,
                  void         *pwdata);

/*
 * GenerateRandom
 */
extern SECStatus
GenerateRandom(unsigned char *rbuf,
               int           rsize);

/*
 * FileToItem
 */
extern SECStatus
FileToItem(SECItem    *dst,
           PRFileDesc *src);

/*
 * SeedFromNoiseFile
 */
extern SECStatus
SeedFromNoiseFile(const char *noiseFileName);

/*
 * FileSize
 */
extern long
FileSize(const char* filename);

/*
 * ReadDERFromFile
 */
extern SECStatus
ReadDERFromFile(SECItem *der, const char *inFileName, PRBool ascii);

#endif /* _UTIL_H */

Util.c

/* 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 https://mozilla.org/MPL/2.0/. */

#include "util.h"

/*
 * These utility functions are adapted from those found in
 * the sectool library used by the NSS security tools and
 * other NSS test applications.
 */

/*
 * Newline
 */
static void
Newline(PRFileDesc* out)
{
    PR_fprintf(out, "\n");
}

/*
 * PrintAsAscii
 */
void
PrintAsAscii(PRFileDesc* out, const unsigned char *data, unsigned int len)
{
    char *b64Data = NULL;

    b64Data = BTOA_DataToAscii(data, len);
    PR_fprintf(out, "%s", b64Data);
    PR_fprintf(out, "\n");
    if (b64Data) {
        PORT_Free(b64Data);
    }
}

/*
 * PrintAsHex
 */
void
PrintAsHex(PRFileDesc* out, const unsigned char *data, unsigned int len)
{
    unsigned i;
    int column;
    unsigned int limit = 15;
    unsigned int level  = 1;

    column = level;
    if (!len) {
        PR_fprintf(out, "(empty)\n");
        return;
    }

    for (i = 0; i < len; i++) {
        if (i != len - 1) {
            PR_fprintf(out, "%02x:", data[i]);
            column += 3;
        } else {
            PR_fprintf(out, "%02x", data[i]);
            column += 2;
            break;
        }
        if (column > 76 || (i % 16 == limit)) {
            Newline(out);
            column = level;
            limit = i % 16;
        }
    }
    if (column != level) {
        Newline(out);
    }
}

/*
 * GetDigit
 */
int
GetDigit(char c)
{
    if (c == 0) {
        return -1;
    }
    if (c <= '9' && c >= '0') {
        return c - '0';
    }
    if (c <= 'f' && c >= 'a') {
        return c - 'a' + 0xa;
    }
    if (c <= 'F' && c >= 'A') {
        return c - 'A' + 0xa;
    }
    return -1;
}

/*
 * HexToBuf
 */
int
HexToBuf(unsigned char *inString, SECItem *outbuf, PRBool isHexData)
{
    int len = strlen((const char *)inString);
    int outLen = len+1/2;
    int trueLen = 0;
    int digit1, digit2;

    outbuf->data = isHexData
        ? PORT_Alloc(outLen)
        : PORT_Alloc(len);
    if (!outbuf->data) {
        return -1;
    }
    if (isHexData) {
        while (*inString) {
             if ((*inString == '\n') || (*inString == ':')) {
                 inString++;
                 continue;
             }
             digit1 = GetDigit(*inString++);
             digit2 = GetDigit(*inString++);
             if ((digit1 == -1) || (digit2 == -1)) {
                 PORT_Free(outbuf->data);
                 outbuf->data = NULL;
                 return -1;
             }
             outbuf->data[trueLen++] = digit1 << 4 | digit2;
        }
    } else {
        while (*inString) {
            if (*inString == '\n') {
                inString++;
                continue;
            }
            outbuf->data[trueLen++] = *inString++;
        }
        outbuf->data[trueLen] = '\0';
        trueLen = trueLen-1;
    }
    outbuf->len = trueLen;
    return 0;
}

/*
 * FileToItem
 */
SECStatus
FileToItem(SECItem *dst, PRFileDesc *src)
{
    PRFileInfo info;
    PRInt32 numBytes;
    PRStatus prStatus;

    prStatus = PR_GetOpenFileInfo(src, &info);

    if (prStatus != PR_SUCCESS) {
        return SECFailure;
    }

    dst->data = 0;
    if (SECITEM_AllocItem(NULL, dst, info.size)) {
        numBytes = PR_Read(src, dst->data, info.size);
        if (numBytes == info.size) {
            return SECSuccess;
        }
    }
    SECITEM_FreeItem(dst, PR_FALSE);
    dst->data = NULL;
    return SECFailure;
}

/*
 * echoOff
 */
static void echoOff(int fd)
{
   if (isatty(fd)) {
       struct termios tio;
       tcgetattr(fd, &tio);
       tio.c_lflag &= ~ECHO;
       tcsetattr(fd, TCSAFLUSH, &tio);
   }
}

/*
 * echoOn
 */
static void echoOn(int fd)
{
   if (isatty(fd)) {
       struct termios tio;
       tcgetattr(fd, &tio);
       tio.c_lflag |= ECHO;
       tcsetattr(fd, TCSAFLUSH, &tio);
   }
}

/*
 * CheckPassword
 */
PRBool CheckPassword(char *cp)
{
    int len;
    char *end;
    len = PORT_Strlen(cp);
    if (len < 8) {
        return PR_FALSE;
    }
    end = cp + len;
    while (cp < end) {
        unsigned char ch = *cp++;
        if (!((ch >= 'A') && (ch <= 'Z')) &&
            !((ch >= 'a') && (ch <= 'z'))) {
            return PR_TRUE;
        }
   }
   return PR_FALSE;
}

/*
 * GetPassword
 */
char* GetPassword(FILE *input, FILE *output, char *prompt,
                  PRBool (*ok)(char *))
{
    char phrase[200] = {'\0'};
    int infd         = fileno(input);
    int isTTY        = isatty(infd);

    for (;;) {
        /* Prompt for password */
        if (isTTY) {
            fprintf(output, "%s", prompt);
            fflush (output);
            echoOff(infd);
        }
        fgets(phrase, sizeof(phrase), input);
        if (isTTY) {
            fprintf(output, "\n");
            echoOn(infd);
        }
        /* stomp on newline */
        phrase[PORT_Strlen(phrase)-1] = 0;
        /* Validate password */
        if (!(*ok)(phrase)) {
            if (!isTTY) return 0;
            fprintf(output, "Password must be at least 8 characters long with one or more\n");
            fprintf(output, "non-alphabetic characters\n");
            continue;
        }
        return (char*) PORT_Strdup(phrase);
    }
}

/*
 * FilePasswd extracts the password from a text file
 *
 * Storing passwords is often used with server environments
 * where prompting the user for a password or requiring it
 * to be entered in the commnd line is not a feasible option.
 *
 * This function supports password extraction from files with
 * multipe passwords, one for each token. In the single password
 * case a line would just have the passord whereas in the multi-
 * password variant they could be of the form
 *
 * token_1_name:its_password
 * token_2_name:its_password
 *
 */
char *
FilePasswd(PK11SlotInfo *slot, PRBool retry, void *arg)
{
    char* phrases, *phrase;
    PRFileDesc *fd;
    PRInt32 nb;
    char *pwFile = arg;
    int i;
    const long maxPwdFileSize = 4096;
    char* tokenName = NULL;
    int tokenLen = 0;

    if (!pwFile)
        return 0;

    if (retry) {
        return 0;  /* no good retrying - the files contents will be the same */
    }

    phrases = PORT_ZAlloc(maxPwdFileSize);

    if (!phrases) {
        return 0; /* out of memory */
    }

    fd = PR_Open(pwFile, PR_RDONLY, 0);
    if (!fd) {
        fprintf(stderr, "No password file \"%s\" exists.\n", pwFile);
        PORT_Free(phrases);
        return NULL;
    }

    nb = PR_Read(fd, phrases, maxPwdFileSize);

    PR_Close(fd);

    if (nb == 0) {
        fprintf(stderr,"password file contains no data\n");
        PORT_Free(phrases);
        return NULL;
    }

    if (slot) {
        tokenName = PK11_GetTokenName(slot);
        if (tokenName) {
            tokenLen = PORT_Strlen(tokenName);
        }
    }
    i = 0;
    do {
        int startphrase = i;
        int phraseLen;

        /* handle the Windows EOL case */
        while (phrases[i] != '\r' && phrases[i] != '\n' && i < nb) i++;

        /* terminate passphrase */
        phrases[i++] = '\0';
        /* clean up any EOL before the start of the next passphrase */
        while ( (i<nb) && (phrases[i] == '\r' || phrases[i] == '\n')) {
            phrases[i++] = '\0';
        }
        /* now analyze the current passphrase */
        phrase = &phrases[startphrase];
        if (!tokenName)
            break;
        if (PORT_Strncmp(phrase, tokenName, tokenLen)) continue;
        phraseLen = PORT_Strlen(phrase);
        if (phraseLen < (tokenLen+1)) continue;
        if (phrase[tokenLen] != ':') continue;
        phrase = &phrase[tokenLen+1];
        break;

    } while (i<nb);

    phrase = PORT_Strdup((char*)phrase);
    PORT_Free(phrases);
    return phrase;
}

/*
 * GetModulePassword
 */
char* GetModulePassword(PK11SlotInfo *slot, int retry, void *arg)
{
    char prompt[255];
    secuPWData *pwdata = (secuPWData *)arg;
    char *pw;

    if (pwdata == NULL) {
        return NULL;
    }

    if (retry && pwdata->source != PW_NONE) {
        PR_fprintf(PR_STDERR, "Incorrect password/PIN entered.\n");
        return NULL;
    }

    switch (pwdata->source) {
    case PW_NONE:
        sprintf(prompt, "Enter Password or Pin for \"%s\":",
                PK11_GetTokenName(slot));
        return GetPassword(stdin, stdout, prompt, CheckPassword);
    case PW_FROMFILE:
        pw = FilePasswd(slot, retry, pwdata->data);
        pwdata->source = PW_PLAINTEXT;
        pwdata->data = PL_strdup(pw);
        return pw;
    case PW_PLAINTEXT:
        return PL_strdup(pwdata->data);
    default:
        break;
    }
    PR_fprintf(PR_STDERR, "Password check failed:  No password found.\n");
    return NULL;
}

/*
 * GenerateRandom
 */
SECStatus
GenerateRandom(unsigned char *rbuf, int rsize)
{
    char meter[] = {
                   "\r|                                |" };
    int            fd,  count;
    int            c;
    SECStatus      rv                  = SECSuccess;
    cc_t           orig_cc_min;
    cc_t           orig_cc_time;
    tcflag_t       orig_lflag;
    struct termios tio;

    fprintf(stderr, "To generate random numbers, "
            "continue typing until the progress meter is full:\n\n");
    fprintf(stderr, "%s", meter);
    fprintf(stderr, "\r|");

    /* turn off echo on stdin & return on 1 char instead of NL */
    fd = fileno(stdin);

    tcgetattr(fd, &tio);
    orig_lflag = tio.c_lflag;
    orig_cc_min = tio.c_cc[VMIN];
    orig_cc_time = tio.c_cc[VTIME];
    tio.c_lflag &= ~ECHO;
    tio.c_lflag &= ~ICANON;
    tio.c_cc[VMIN] = 1;
    tio.c_cc[VTIME] = 0;
    tcsetattr(fd, TCSAFLUSH, &tio);
    /* Get random noise from keyboard strokes */
    count = 0;
    while (count < rsize) {
        c = getc(stdin);
        if (c == EOF) {
            rv = SECFailure;
            break;
        }
        *(rbuf + count) = c;
        if (count == 0 || c != *(rbuf + count -1)) {
            count++;
            fprintf(stderr, "*");
        }
    }
    rbuf[count] = '\0';

    fprintf(stderr, "\n\nFinished.  Press enter to continue: ");
    while ((c = getc(stdin)) != '\n' && c != EOF)
        ;
    if (c == EOF)
        rv = SECFailure;
    fprintf(stderr, "\n");

    /* set back termio the way it was */
    tio.c_lflag = orig_lflag;
    tio.c_cc[VMIN] = orig_cc_min;
    tio.c_cc[VTIME] = orig_cc_time;
    tcsetattr(fd, TCSAFLUSH, &tio);
    return rv;
}

/*
 * SeedFromNoiseFile
 */
SECStatus
SeedFromNoiseFile(const char *noiseFileName)
{
    char buf[2048];
    PRFileDesc *fd;
    PRInt32 count;

    fd = PR_Open(noiseFileName, PR_RDONLY, 0);
    if (!fd) {
        fprintf(stderr, "failed to open noise file.");
        return SECFailure;
    }

    do {
        count = PR_Read(fd,buf,sizeof(buf));
        if (count > 0) {
            PK11_RandomUpdate(buf,count);
        }
    } while (count > 0);

    PR_Close(fd);
    return SECSuccess;
}

/*
 * FileSize
 */
long FileSize(const char* filename)
{
    struct stat stbuf;
    stat(filename, &stbuf);
    return stbuf.st_size;
}

/*
 *  ReadDERFromFile
 */
SECStatus
ReadDERFromFile(SECItem *der, const char *inFileName, PRBool ascii)
{
    SECStatus rv       = SECSuccess;
    PRFileDesc *inFile = NULL;

    inFile = PR_Open(inFileName, PR_RDONLY, 0);
    if (!inFile) {
        PR_fprintf(PR_STDERR, "Failed to open file \"%s\" (%ld, %ld).\n",
                   inFileName, PR_GetError(), PR_GetOSError());
        rv = SECFailure;
        goto cleanup;
    }

    if (ascii) {
        /* First convert ascii to binary */
        SECItem filedata;
        char *asc, *body;

        /* Read in ascii data */
        rv = FileToItem(&filedata, inFile);
        asc = (char *)filedata.data;
        if (!asc) {
            PR_fprintf(PR_STDERR, "unable to read data from input file\n");
            rv = SECFailure;
            goto cleanup;
        }

        /* check for headers and trailers and remove them */
        if ((body = strstr(asc, "-----BEGIN")) != NULL) {
            char *trailer = NULL;
            asc = body;
            body = PORT_Strchr(body, '\n');
            if (!body)
                body = PORT_Strchr(asc, '\r'); /* maybe this is a MAC file */
            if (body)
                trailer = strstr(++body, "-----END");
            if (trailer != NULL) {
                *trailer = '\0';
            } else {
                PR_fprintf(PR_STDERR,  "input has header but no trailer\n");
                PORT_Free(filedata.data);
                rv = SECFailure;
                goto cleanup;
            }
        } else {
            body = asc;
        }

        /* Convert to binary */
        rv = ATOB_ConvertAsciiToItem(der, body);
        if (rv) {
            PR_fprintf(PR_STDERR,  "error converting ascii to binary %s\n",
                       PORT_GetError());
            PORT_Free(filedata.data);
            rv = SECFailure;
            goto cleanup;
        }

        PORT_Free(filedata.data);
    } else {
        /* Read in binary der */
        rv = FileToItem(der, inFile);
        if (rv) {
            PR_fprintf(PR_STDERR, "error converting der \n");
            rv = SECFailure;
        }
    }
cleanup:
    if (inFile) {
        PR_Close(inFile);
    }
    return rv;
}