Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  secutil.c   Sprache: 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 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 "cryptohi.h"
#include "secutil.h"
#include "secpkcs7.h"
#include "secpkcs5.h"
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>

#ifdef XP_UNIX
#include <unistd.h>
#endif

/* for SEC_TraverseNames */
#include "cert.h"
#include "certt.h"
#include "certdb.h"

#include "secmod.h"
#include "pk11func.h"
#include "secoid.h"

static char consoleName[] = {
#ifdef XP_UNIX
    "/dev/tty"
#else
    "CON:"
#endif
};

#include "cert.h"
#include "nssutil.h"
#include "ssl.h"
#include "sslproto.h"
#include "xconst.h"

#define DEFN_EXTEN_EXT_VALUE_ENCODER(mmm)                                       \
    SECStatus EXTEN_EXT_VALUE_ENCODER_##mmm(PLArenaPool *extHandleArena,        \
                                            void *value, SECItem *encodedValue) \
    {                                                                           \
        return mmm(extHandleArena, value, encodedValue);                        \
    }

DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeAltNameExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeAuthKeyID)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeBasicConstraintValue)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeCRLDistributionPoints)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeCertPoliciesExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeInfoAccessExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeInhibitAnyExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeNameConstraintsExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodePolicyConstraintsExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodePolicyMappingExtension)
DEFN_EXTEN_EXT_VALUE_ENCODER(CERT_EncodeSubjectKeyID)

static PRBool utf8DisplayEnabled = PR_FALSE;

/* The minimum password/pin length (in Unicode characters) in FIPS mode,
 * defined in lib/softoken/pkcs11i.h. */

#define FIPS_MIN_PIN 7

void
SECU_EnableUtf8Display(PRBool enable)
{
    utf8DisplayEnabled = enable;
}

PRBool
SECU_GetUtf8DisplayEnabled(void)
{
    return utf8DisplayEnabled;
}

static void
secu_ClearPassword(char *p)
{
    if (p) {
        PORT_Memset(p, 0, PORT_Strlen(p));
        PORT_Free(p);
    }
}

char *
SECU_GetPasswordString(void *arg, char *prompt)
{
#ifndef _WINDOWS
    char *p = NULL;
    FILE *input, *output;

    /* open terminal */
    input = fopen(consoleName, "r");
    if (input == NULL) {
        fprintf(stderr, "Error opening input terminal for read\n");
        return NULL;
    }

    output = fopen(consoleName, "w");
    if (output == NULL) {
        fprintf(stderr, "Error opening output terminal for write\n");
        fclose(input);
        return NULL;
    }

    p = SEC_GetPassword(input, output, prompt, SEC_BlindCheckPassword);

    fclose(input);
    fclose(output);

    return p;

#else
    /* Win32 version of above. opening the console may fail
       on windows95, and certainly isn't necessary.. */


    char *p = NULL;

    p = SEC_GetPassword(stdin, stdout, prompt, SEC_BlindCheckPassword);
    return p;

#endif
}

/*
 *  p a s s w o r d _ h a r d c o d e
 *
 *  A function to use the password passed in the -f(pwfile) argument
 *  of the command line.
 *  After use once, null it out otherwise PKCS11 calls us forever.?
 *
 */

char *
SECU_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;
}

char *
SECU_GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg)
{
    char prompt[255];
    secuPWData *pwdata = (secuPWData *)arg;
    secuPWData pwnull = { PW_NONE, 0 };
    secuPWData pwxtrn = { PW_EXTERNAL, "external" };

    if (pwdata == NULL)
        pwdata = &pwnull;

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

    switch (pwdata->source) {
        case PW_NONE:
            snprintf(prompt, sizeof(prompt), "Enter Password or Pin for \"%s\":",
                     PK11_GetTokenName(slot));
            return SECU_GetPasswordString(NULL, prompt);
        case PW_FROMFILE:
            return SECU_FilePasswd(slot, retry, pwdata->data);
        case PW_EXTERNAL:
            snprintf(prompt, sizeof(prompt),
                     "Press Enter, then enter PIN for \"%s\" on external device.\n",
                     PK11_GetTokenName(slot));
            char *pw = SECU_GetPasswordString(NULL, prompt);
            PORT_Free(pw);
        /* Fall Through */
        case PW_PLAINTEXT:
            return PL_strdup(pwdata->data);
        default:
            break;
    }

    PR_fprintf(PR_STDERR, "Password check failed: No password found.\n");
    return NULL;
}

char *
secu_InitSlotPassword(PK11SlotInfo *slot, PRBool retry, void *arg)
{
    char *p0 = NULL;
    char *p1 = NULL;
    FILE *input, *output;
    secuPWData *pwdata = arg;

    if (pwdata->source == PW_FROMFILE) {
        return SECU_FilePasswd(slot, retry, pwdata->data);
    }
    if (pwdata->source == PW_PLAINTEXT) {
        return PL_strdup(pwdata->data);
    }

/* PW_NONE - get it from tty */
/* open terminal */
#ifdef _WINDOWS
    input = stdin;
#else
    input = fopen(consoleName, "r");
#endif
    if (input == NULL) {
        PR_fprintf(PR_STDERR, "Error opening input terminal for read\n");
        return NULL;
    }

    /* we have no password, so initialize database with one */
    if (PK11_IsFIPS()) {
        PR_fprintf(PR_STDERR,
                   "Enter a password which will be used to encrypt your keys.\n"
                   "The password should be at least %d characters long,\n"
                   "and should consist of at least three character classes.\n"
                   "The available character classes are: digits (0-9), ASCII\n"
                   "lowercase letters, ASCII uppercase letters, ASCII\n"
                   "non-alphanumeric characters, and non-ASCII characters.\n\n"
                   "If an ASCII uppercase letter appears at the beginning of\n"
                   "the password, it is not counted toward its character class.\n"
                   "Similarly, if a digit appears at the end of the password,\n"
                   "it is not counted toward its character class.\n\n",
                   FIPS_MIN_PIN);
    } else {
        PR_fprintf(PR_STDERR,
                   "Enter a password which will be used to encrypt your keys.\n"
                   "The password should be at least 8 characters long,\n"
                   "and should contain at least one non-alphabetic character.\n\n");
    }

    output = fopen(consoleName, "w");
    if (output == NULL) {
        PR_fprintf(PR_STDERR, "Error opening output terminal for write\n");
#ifndef _WINDOWS
        fclose(input);
#endif
        return NULL;
    }

    for (;;) {
        if (p0)
            PORT_Free(p0);
        p0 = SEC_GetPassword(input, output, "Enter new password: ",
                             SEC_BlindCheckPassword);

        if (p1)
            PORT_Free(p1);
        p1 = SEC_GetPassword(input, output, "Re-enter password: ",
                             SEC_BlindCheckPassword);
        if (p0 && p1 && !PORT_Strcmp(p0, p1)) {
            break;
        }
        PR_fprintf(PR_STDERR, "Passwords do not match. Try again.\n");
    }

    /* clear out the duplicate password string */
    secu_ClearPassword(p1);

    fclose(input);
    fclose(output);

    return p0;
}

SECStatus
SECU_ChangePW(PK11SlotInfo *slot, char *passwd, char *pwFile)
{
    return SECU_ChangePW2(slot, passwd, 0, pwFile, 0);
}

SECStatus
SECU_ChangePW2(PK11SlotInfo *slot, char *oldPass, char *newPass,
               char *oldPwFile, char *newPwFile)
{
    SECStatus rv;
    secuPWData pwdata, newpwdata;
    char *oldpw = NULL, *newpw = NULL;

    if (oldPass) {
        pwdata.source = PW_PLAINTEXT;
        pwdata.data = oldPass;
    } else if (oldPwFile) {
        pwdata.source = PW_FROMFILE;
        pwdata.data = oldPwFile;
    } else {
        pwdata.source = PW_NONE;
        pwdata.data = NULL;
    }

    if (newPass) {
        newpwdata.source = PW_PLAINTEXT;
        newpwdata.data = newPass;
    } else if (newPwFile) {
        newpwdata.source = PW_FROMFILE;
        newpwdata.data = newPwFile;
    } else {
        newpwdata.source = PW_NONE;
        newpwdata.data = NULL;
    }

    if (PK11_NeedUserInit(slot)) {
        newpw = secu_InitSlotPassword(slot, PR_FALSE, &pwdata);
        rv = PK11_InitPin(slot, (char *)NULL, newpw);
        goto done;
    }

    for (;;) {
        oldpw = SECU_GetModulePassword(slot, PR_FALSE, &pwdata);

        if (PK11_CheckUserPassword(slot, oldpw) != SECSuccess) {
            if (pwdata.source == PW_NONE) {
                PR_fprintf(PR_STDERR, "Invalid password. Try again.\n");
            } else {
                PR_fprintf(PR_STDERR, "Invalid password.\n");
                PORT_Memset(oldpw, 0, PL_strlen(oldpw));
                PORT_Free(oldpw);
                rv = SECFailure;
                goto done;
            }
        } else
            break;

        PORT_Free(oldpw);
    }

    newpw = secu_InitSlotPassword(slot, PR_FALSE, &newpwdata);

    rv = PK11_ChangePW(slot, oldpw, newpw);
    if (rv != SECSuccess) {
        PR_fprintf(PR_STDERR, "Failed to change password.\n");
    } else {
        PR_fprintf(PR_STDOUT, "Password changed successfully.\n");
    }

    PORT_Memset(oldpw, 0, PL_strlen(oldpw));
    PORT_Free(oldpw);

done:
    if (newpw) {
        PORT_Memset(newpw, 0, PL_strlen(newpw));
        PORT_Free(newpw);
    }
    return rv;
}

struct matchobj {
    SECItem index;
    char *nname;
    PRBool found;
};

char *
SECU_DefaultSSLDir(void)
{
    char *dir;
    static char sslDir[1000];

    dir = PR_GetEnvSecure("SSL_DIR");
    if (!dir)
        return NULL;

    if (strlen(dir) >= PR_ARRAY_SIZE(sslDir)) {
        return NULL;
    }
    snprintf(sslDir, sizeof(sslDir), "%s", dir);

    if (sslDir[strlen(sslDir) - 1] == '/')
        sslDir[strlen(sslDir) - 1] = 0;

    return sslDir;
}

char *
SECU_AppendFilenameToDir(char *dir, char *filename)
{
    static char path[1000];

    if (dir[strlen(dir) - 1] == '/')
        snprintf(path, sizeof(path), "%s%s", dir, filename);
    else
        snprintf(path, sizeof(path), "%s/%s", dir, filename);
    return path;
}

char *
SECU_ConfigDirectory(const char *base)
{
    static PRBool initted = PR_FALSE;
    const char *dir = ".netscape";
    char *home;
    static char buf[1000];

    if (initted)
        return buf;

    if (base == NULL || *base == 0) {
        home = PR_GetEnvSecure("HOME");
        if (!home)
            home = "";

        if (*home && home[strlen(home) - 1] == '/')
            snprintf(buf, sizeof(buf), "%.900s%s", home, dir);
        else
            snprintf(buf, sizeof(buf), "%.900s/%s", home, dir);
    } else {
        snprintf(buf, sizeof(buf), "%.900s", base);
        if (buf[strlen(buf) - 1] == '/')
            buf[strlen(buf) - 1] = 0;
    }

    initted = PR_TRUE;
    return buf;
}

SECStatus
SECU_ReadDERFromFile(SECItem *der, PRFileDesc *inFile, PRBool ascii,
                     PRBool warnOnPrivateKeyInAsciiFile)
{
    SECStatus rv;
    if (ascii) {
        /* First convert ascii to binary */
        SECItem filedata;

        /* Read in ascii data */
        rv = SECU_FileToItem(&filedata, inFile);
        if (rv != SECSuccess)
            return rv;
        if (!filedata.data) {
            fprintf(stderr, "unable to read data from input file\n");
            return SECFailure;
        }
        /* need one additional byte for zero terminator */
        rv = SECITEM_ReallocItemV2(NULL, &filedata, filedata.len + 1);
        if (rv != SECSuccess) {
            PORT_Free(filedata.data);
            return rv;
        }
        char *asc = (char *)filedata.data;
        asc[filedata.len - 1] = '\0';

        if (warnOnPrivateKeyInAsciiFile && strstr(asc, "PRIVATE KEY")) {
            fprintf(stderr, "Warning: ignoring private key. Consider to use "
                            "pk12util.\n");
        }

        char *body;
        /* 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 {
                fprintf(stderr, "input has header but no trailer\n");
                PORT_Free(filedata.data);
                return SECFailure;
            }
        } else {
            body = asc;
        }

        /* Convert to binary */
        rv = ATOB_ConvertAsciiToItem(der, body);
        if (rv != SECSuccess) {
            fprintf(stderr, "error converting ascii to binary (%s)\n",
                    SECU_Strerror(PORT_GetError()));
            PORT_Free(filedata.data);
            return SECFailure;
        }

        PORT_Free(filedata.data);
    } else {
        /* Read in binary der */
        rv = SECU_FileToItem(der, inFile);
        if (rv != SECSuccess) {
            fprintf(stderr, "error converting der (%s)\n",
                    SECU_Strerror(PORT_GetError()));
            return SECFailure;
        }
    }
    return SECSuccess;
}

#define INDENT_MULT 4

/*
 * remove the tag and length and just leave the bare BER data
 */

SECStatus
SECU_StripTagAndLength(SECItem *i)
{
    unsigned int start;
    PRBool isIndefinite;

    if (!i || !i->data || i->len < 2) { /* must be at least tag and length */
        PORT_SetError(SEC_ERROR_BAD_DER);
        return SECFailure;
    }
    isIndefinite = (i->data[1] == 0x80);
    start = ((i->data[1] & 0x80) ? (i->data[1] & 0x7f) + 2 : 2);
    if (i->len < start) {
        PORT_SetError(SEC_ERROR_BAD_DER);
        return SECFailure;
    }
    i->data += start;
    i->len -= start;
    /* we are using indefinite encoding, drop the trailing zero */
    if (isIndefinite) {
        if (i->len <= 1) {
            PORT_SetError(SEC_ERROR_BAD_DER);
            return SECFailure;
        }
        /* verify tags are zero */
        if ((i->data[i->len - 1] != 0) || (i->data[i->len - 2] != 0)) {
            PORT_SetError(SEC_ERROR_BAD_DER);
            return SECFailure;
        }
        i->len -= 2;
    }

    return SECSuccess;
}

/*
 * Create a new SECItem which points to the current BER tag and length with
 * all it's data. For indefinite encoding, this will also include the trailing
 * indefinite markers
 * The 'in' item is advanced to point to the next BER tag.
 * You don't want to use this in an actual BER/DER parser as NSS already
 * has 3 to choose from)
 */

SECStatus
SECU_ExtractBERAndStep(SECItem *in, SECItem *out)
{
    if (!in || !in->data || in->len < 2) { /* must be at least tag and length */
        PORT_SetError(SEC_ERROR_BAD_DER);
        return SECFailure;
    }

    *out = *in;

    /* first handle indefinite encoding */
    if (out->data[1] == 0x80) {
        SECItem this = *out;
        SECItem next;
        this.data += 2;
        this.len -= 2;
        out->len = 2;
        /* walk through all the entries until we find the '0' */
        while ((this.len >= 2) && (this.data[0] != 0)) {
            SECStatus rv = SECU_ExtractBERAndStep(&this, &next);
            if (rv != SECSuccess) {
                return rv;
            }
            out->len += next.len;
        }
        if ((this.len < 2) || ((this.data[0] != 0) && (this.data[1] != 0))) {
            PORT_SetError(SEC_ERROR_BAD_DER);
            return SECFailure;
        }
        out->len += 2; /* include the trailing zeros */
        in->data += out->len;
        in->len -= out->len;
        return SECSuccess;
    }

    /* now handle normal DER encoding */
    if (out->data[1] & 0x80) {
        unsigned int i;
        unsigned int lenlen = out->data[1] & 0x7f;
        unsigned int len = 0;
        if (lenlen > sizeof out->len) {
            PORT_SetError(SEC_ERROR_BAD_DER);
            return SECFailure;
        }
        for (i = 0; i < lenlen; i++) {
            len = (len << 8) | out->data[2 + i];
        }
        out->len = len + lenlen + 2;
    } else {
        out->len = out->data[1] + 2;
    }
    if (out->len > in->len) {
        /* we've ran into a truncated file */
        PORT_SetError(SEC_ERROR_BAD_DER);
        return SECFailure;
    }
    in->data += out->len;
    in->len -= out->len;
    return SECSuccess;
}

static void
secu_PrintRawStringQuotesOptional(FILE *out, SECItem *si, const char *m,
                                  int level, PRBool quotes)
{
    int column;
    unsigned int i;

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s: ", m);
        column = (level * INDENT_MULT) + strlen(m) + 2;
        level++;
    } else {
        SECU_Indent(out, level);
        column = level * INDENT_MULT;
    }
    if (quotes) {
        fprintf(out, "\"");
        column++;
    }

    for (i = 0; i < si->len; i++) {
        unsigned char val = si->data[i];
        unsigned char c;
        if (SECU_GetWrapEnabled() && column > 76) {
            SECU_Newline(out);
            SECU_Indent(out, level);
            column = level * INDENT_MULT;
        }

        if (utf8DisplayEnabled) {
            if (val < 32)
                c = '.';
            else
                c = val;
        } else {
            c = printable[val];
        }
        fprintf(out, "%c", c);
        column++;
    }

    if (quotes) {
        fprintf(out, "\"");
        column++;
    }
    if (SECU_GetWrapEnabled() &&
        (column != level * INDENT_MULT || column > 76)) {
        SECU_Newline(out);
    }
}

static void
secu_PrintRawString(FILE *out, SECItem *si, const char *m, int level)
{
    secu_PrintRawStringQuotesOptional(out, si, m, level, PR_TRUE);
}

void
SECU_PrintString(FILE *out, const SECItem *si, const char *m, int level)
{
    SECItem my = *si;

    if (SECSuccess != SECU_StripTagAndLength(&my) || !my.len)
        return;
    secu_PrintRawString(out, &my, m, level);
}

/* print an unencoded boolean */
static void
secu_PrintBoolean(FILE *out, SECItem *i, const char *m, int level)
{
    int val = 0;

    if (i->data && i->len) {
        val = i->data[0];
    }

    if (!m) {
        m = "Boolean";
    }
    SECU_Indent(out, level);
    fprintf(out, "%s: %s\n", m, (val ? "True" : "False"));
}

/*
 * Format and print "time".  If the tag message "m" is not NULL,
 * do indent formatting based on "level" and add a newline afterward;
 * otherwise just print the formatted time string only.
 */

static void
secu_PrintTime(FILE *out, const PRTime time, const char *m, int level)
{
    PRExplodedTime printableTime;
    char *timeString;

    /* Convert to local time */
    PR_ExplodeTime(time, PR_GMTParameters, &printableTime);

    timeString = PORT_Alloc(256);
    if (timeString == NULL)
        return;

    if (m != NULL) {
        SECU_Indent(out, level);
        fprintf(out, "%s: ", m);
    }

    if (PR_FormatTime(timeString, 256, "%a %b %d %H:%M:%S %Y", &printableTime)) {
        fputs(timeString, out);
    }

    if (m != NULL)
        fprintf(out, "\n");

    PORT_Free(timeString);
}

/*
 * Format and print the UTC Time "t".  If the tag message "m" is not NULL,
 * do indent formatting based on "level" and add a newline afterward;
 * otherwise just print the formatted time string only.
 */

void
SECU_PrintUTCTime(FILE *out, const SECItem *t, const char *m, int level)
{
    PRTime time;
    SECStatus rv;

    rv = DER_UTCTimeToTime(&time, t);
    if (rv != SECSuccess)
        return;

    secu_PrintTime(out, time, m, level);
}

/*
 * Format and print the Generalized Time "t".  If the tag message "m"
 * is not NULL, * do indent formatting based on "level" and add a newline
 * afterward; otherwise just print the formatted time string only.
 */

void
SECU_PrintGeneralizedTime(FILE *out, const SECItem *t, const char *m, int level)
{
    PRTime time;
    SECStatus rv;

    rv = DER_GeneralizedTimeToTime(&time, t);
    if (rv != SECSuccess)
        return;

    secu_PrintTime(out, time, m, level);
}

/*
 * Format and print the UTC or Generalized Time "t".  If the tag message
 * "m" is not NULL, do indent formatting based on "level" and add a newline
 * afterward; otherwise just print the formatted time string only.
 */

void
SECU_PrintTimeChoice(FILE *out, const SECItem *t, const char *m, int level)
{
    switch (t->type) {
        case siUTCTime:
            SECU_PrintUTCTime(out, t, m, level);
            break;

        case siGeneralizedTime:
            SECU_PrintGeneralizedTime(out, t, m, level);
            break;

        default:
            PORT_Assert(0);
            break;
    }
}

/* This prints a SET or SEQUENCE */
static void
SECU_PrintSet(FILE *out, const SECItem *t, const char *m, int level)
{
    int type = t->data[0] & SEC_ASN1_TAGNUM_MASK;
    int constructed = t->data[0] & SEC_ASN1_CONSTRUCTED;
    const char *label;
    SECItem my = *t;

    if (!constructed) {
        SECU_PrintAsHex(out, t, m, level);
        return;
    }
    if (SECSuccess != SECU_StripTagAndLength(&my))
        return;

    SECU_Indent(out, level);
    if (m) {
        fprintf(out, "%s: ", m);
    }

    if (type == SEC_ASN1_SET)
        label = "Set ";
    else if (type == SEC_ASN1_SEQUENCE)
        label = "Sequence ";
    else
        label = "";
    fprintf(out, "%s{\n", label); /* } */

    while (my.len >= 2) {
        SECItem tmp;
        if (SECSuccess != SECU_ExtractBERAndStep(&my, &tmp)) {
            break;
        }
        SECU_PrintAny(out, &tmp, NULL, level + 1);
    }
    SECU_Indent(out, level);
    fprintf(out, /* { */ "}\n");
}

static void
secu_PrintContextSpecific(FILE *out, const SECItem *i, const char *m, int level)
{
    int type = i->data[0] & SEC_ASN1_TAGNUM_MASK;
    int constructed = i->data[0] & SEC_ASN1_CONSTRUCTED;
    SECItem tmp;

    if (constructed) {
        char *m2;
        if (!m)
            m2 = PR_smprintf("[%d]", type);
        else
            m2 = PR_smprintf("%s: [%d]", m, type);
        if (m2) {
            SECU_PrintSet(out, i, m2, level);
            PR_smprintf_free(m2);
        }
        return;
    }

    SECU_Indent(out, level);
    if (m) {
        fprintf(out, "%s: ", m);
    }
    fprintf(out, "[%d]\n", type);

    tmp = *i;
    if (SECSuccess == SECU_StripTagAndLength(&tmp))
        SECU_PrintAsHex(out, &tmp, m, level + 1);
}

static void
secu_PrintOctetString(FILE *out, const SECItem *i, const char *m, int level)
{
    SECItem tmp = *i;
    if (SECSuccess == SECU_StripTagAndLength(&tmp))
        SECU_PrintAsHex(out, &tmp, m, level);
}

static void
secu_PrintBitString(FILE *out, const SECItem *i, const char *m, int level)
{
    int unused_bits;
    SECItem tmp = *i;

    if (SECSuccess != SECU_StripTagAndLength(&tmp) || tmp.len < 2)
        return;

    unused_bits = *tmp.data++;
    tmp.len--;

    SECU_PrintAsHex(out, &tmp, m, level);
    if (unused_bits) {
        SECU_Indent(out, level + 1);
        fprintf(out, "(%d least significant bits unused)\n", unused_bits);
    }
}

/* in a decoded bit string, the len member is a bit length. */
static void
secu_PrintDecodedBitString(FILE *out, const SECItem *i, const char *m, int level)
{
    int unused_bits;
    SECItem tmp = *i;

    unused_bits = (tmp.len & 0x7) ? 8 - (tmp.len & 7) : 0;
    DER_ConvertBitString(&tmp); /* convert length to byte length */

    SECU_PrintAsHex(out, &tmp, m, level);
    if (unused_bits) {
        SECU_Indent(out, level + 1);
        fprintf(out, "(%d least significant bits unused)\n", unused_bits);
    }
}

/* Print a DER encoded Boolean */
void
SECU_PrintEncodedBoolean(FILE *out, const SECItem *i, const char *m, int level)
{
    SECItem my = *i;
    if (SECSuccess == SECU_StripTagAndLength(&my))
        secu_PrintBoolean(out, &my, m, level);
}

/* Print a DER encoded integer */
void
SECU_PrintEncodedInteger(FILE *out, const SECItem *i, const char *m, int level)
{
    SECItem my = *i;
    if (SECSuccess == SECU_StripTagAndLength(&my))
        SECU_PrintInteger(out, &my, m, level);
}

/* Print a DER encoded OID */
SECOidTag
SECU_PrintEncodedObjectID(FILE *out, const SECItem *i, const char *m, int level)
{
    SECItem my = *i;
    SECOidTag tag = SEC_OID_UNKNOWN;
    if (SECSuccess == SECU_StripTagAndLength(&my))
        tag = SECU_PrintObjectID(out, &my, m, level);
    return tag;
}

static void
secu_PrintBMPString(FILE *out, const SECItem *i, const char *m, int level)
{
    unsigned char *s;
    unsigned char *d;
    int len;
    SECItem tmp = { 0, 0, 0 };
    SECItem my = *i;

    if (SECSuccess != SECU_StripTagAndLength(&my))
        goto loser;
    if (my.len % 2)
        goto loser;
    len = (int)(my.len / 2);
    tmp.data = (unsigned char *)PORT_Alloc(len);
    if (!tmp.data)
        goto loser;
    tmp.len = len;
    for (s = my.data, d = tmp.data; len > 0; len--) {
        PRUint32 bmpChar = (s[0] << 8) | s[1];
        s += 2;
        if (!isprint(bmpChar))
            goto loser;
        *d++ = (unsigned char)bmpChar;
    }
    secu_PrintRawString(out, &tmp, m, level);
    PORT_Free(tmp.data);
    return;

loser:
    SECU_PrintAsHex(out, i, m, level);
    if (tmp.data)
        PORT_Free(tmp.data);
}

static void
secu_PrintUniversalString(FILE *out, const SECItem *i, const char *m, int level)
{
    unsigned char *s;
    unsigned char *d;
    int len;
    SECItem tmp = { 0, 0, 0 };
    SECItem my = *i;

    if (SECSuccess != SECU_StripTagAndLength(&my))
        goto loser;
    if (my.len % 4)
        goto loser;
    len = (int)(my.len / 4);
    tmp.data = (unsigned char *)PORT_Alloc(len);
    if (!tmp.data)
        goto loser;
    tmp.len = len;
    for (s = my.data, d = tmp.data; len > 0; len--) {
        PRUint32 bmpChar = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
        s += 4;
        if (!isprint(bmpChar & 0xFF))
            goto loser;
        *d++ = (unsigned char)bmpChar;
    }
    secu_PrintRawString(out, &tmp, m, level);
    PORT_Free(tmp.data);
    return;

loser:
    SECU_PrintAsHex(out, i, m, level);
    if (tmp.data)
        PORT_Free(tmp.data);
}

static void
secu_PrintUniversal(FILE *out, const SECItem *i, const char *m, int level)
{
    switch (i->data[0] & SEC_ASN1_TAGNUM_MASK) {
        case SEC_ASN1_ENUMERATED:
        case SEC_ASN1_INTEGER:
            SECU_PrintEncodedInteger(out, i, m, level);
            break;
        case SEC_ASN1_OBJECT_ID:
            SECU_PrintEncodedObjectID(out, i, m, level);
            break;
        case SEC_ASN1_BOOLEAN:
            SECU_PrintEncodedBoolean(out, i, m, level);
            break;
        case SEC_ASN1_UTF8_STRING:
        case SEC_ASN1_PRINTABLE_STRING:
        case SEC_ASN1_VISIBLE_STRING:
        case SEC_ASN1_IA5_STRING:
        case SEC_ASN1_T61_STRING:
            SECU_PrintString(out, i, m, level);
            break;
        case SEC_ASN1_GENERALIZED_TIME:
            SECU_PrintGeneralizedTime(out, i, m, level);
            break;
        case SEC_ASN1_UTC_TIME:
            SECU_PrintUTCTime(out, i, m, level);
            break;
        case SEC_ASN1_NULL:
            SECU_Indent(out, level);
            if (m && m[0])
                fprintf(out, "%s: NULL\n", m);
            else
                fprintf(out, "NULL\n");
            break;
        case SEC_ASN1_SET:
        case SEC_ASN1_SEQUENCE:
            SECU_PrintSet(out, i, m, level);
            break;
        case SEC_ASN1_OCTET_STRING:
            secu_PrintOctetString(out, i, m, level);
            break;
        case SEC_ASN1_BIT_STRING:
            secu_PrintBitString(out, i, m, level);
            break;
        case SEC_ASN1_BMP_STRING:
            secu_PrintBMPString(out, i, m, level);
            break;
        case SEC_ASN1_UNIVERSAL_STRING:
            secu_PrintUniversalString(out, i, m, level);
            break;
        default:
            SECU_PrintAsHex(out, i, m, level);
            break;
    }
}

void
SECU_PrintAny(FILE *out, const SECItem *i, const char *m, int level)
{
    if (i && i->len && i->data) {
        switch (i->data[0] & SEC_ASN1_CLASS_MASK) {
            case SEC_ASN1_CONTEXT_SPECIFIC:
                secu_PrintContextSpecific(out, i, m, level);
                break;
            case SEC_ASN1_UNIVERSAL:
                secu_PrintUniversal(out, i, m, level);
                break;
            default:
                SECU_PrintAsHex(out, i, m, level);
                break;
        }
    }
}

static int
secu_PrintValidity(FILE *out, CERTValidity *v, char *m, int level)
{
    SECU_Indent(out, level);
    fprintf(out, "%s:\n", m);
    SECU_PrintTimeChoice(out, &v->notBefore, "Not Before", level + 1);
    SECU_PrintTimeChoice(out, &v->notAfter, "Not After ", level + 1);
    return 0;
}

/* This function does NOT expect a DER type and length. */
SECOidTag
SECU_PrintObjectID(FILE *out, const SECItem *oid, const char *m, int level)
{
    SECOidData *oiddata;
    char *oidString = NULL;

    oiddata = SECOID_FindOID(oid);
    if (oiddata != NULL) {
        const char *name = oiddata->desc;
        SECU_Indent(out, level);
        if (m != NULL)
            fprintf(out, "%s: ", m);
        fprintf(out, "%s\n", name);
        return oiddata->offset;
    }
    oidString = CERT_GetOidString(oid);
    if (oidString) {
        SECU_Indent(out, level);
        if (m != NULL)
            fprintf(out, "%s: ", m);
        fprintf(out, "%s\n", oidString);
        PR_smprintf_free(oidString);
        return SEC_OID_UNKNOWN;
    }
    SECU_PrintAsHex(out, oid, m, level);
    return SEC_OID_UNKNOWN;
}

typedef struct secuPBEParamsStr {
    SECItem salt;
    SECItem iterationCount;
    SECItem keyLength;
    SECAlgorithmID cipherAlg;
    SECAlgorithmID kdfAlg;
} secuPBEParams;

SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)

/* SECOID_PKCS5_PBKDF2 */
const SEC_ASN1Template secuKDF2Params[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(secuPBEParams) },
    { SEC_ASN1_OCTET_STRING, offsetof(secuPBEParams, salt) },
    { SEC_ASN1_INTEGER, offsetof(secuPBEParams, iterationCount) },
    { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(secuPBEParams, keyLength) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN | SEC_ASN1_OPTIONAL, offsetof(secuPBEParams, kdfAlg),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { 0 }
};

/* PKCS5v1 & PKCS12 */
const SEC_ASN1Template secuPBEParamsTemp[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(secuPBEParams) },
    { SEC_ASN1_OCTET_STRING, offsetof(secuPBEParams, salt) },
    { SEC_ASN1_INTEGER, offsetof(secuPBEParams, iterationCount) },
    { 0 }
};

/* SEC_OID_PKCS5_PBES2, SEC_OID_PKCS5_PBMAC1 */
const SEC_ASN1Template secuPBEV2Params[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(secuPBEParams) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(secuPBEParams, kdfAlg),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(secuPBEParams, cipherAlg),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { 0 }
};

void
secu_PrintRSAPSSParams(FILE *out, SECItem *value, char *m, int level)
{
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    SECStatus rv;
    SECKEYRSAPSSParams param;
    SECAlgorithmID maskHashAlg;

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s:\n", m);
    }

    if (!pool) {
        SECU_Indent(out, level);
        fprintf(out, "Out of memory\n");
        return;
    }

    PORT_Memset(¶m, 0, sizeof param);

    rv = SEC_QuickDERDecodeItem(pool, ¶m,
                                SEC_ASN1_GET(SECKEY_RSAPSSParamsTemplate),
                                value);
    if (rv == SECSuccess) {
        if (!param.hashAlg) {
            SECU_Indent(out, level + 1);
            fprintf(out, "Hash algorithm: default, SHA-1\n");
        } else {
            SECU_PrintObjectID(out, ¶m.hashAlg->algorithm,
                               "Hash algorithm", level + 1);
        }
        if (!param.maskAlg) {
            SECU_Indent(out, level + 1);
            fprintf(out, "Mask algorithm: default, MGF1\n");
            SECU_Indent(out, level + 1);
            fprintf(out, "Mask hash algorithm: default, SHA-1\n");
        } else {
            SECU_PrintObjectID(out, ¶m.maskAlg->algorithm,
                               "Mask algorithm", level + 1);
            rv = SEC_QuickDERDecodeItem(pool, &maskHashAlg,
                                        SEC_ASN1_GET(SECOID_AlgorithmIDTemplate),
                                        ¶m.maskAlg->parameters);
            if (rv == SECSuccess) {
                SECU_PrintObjectID(out, &maskHashAlg.algorithm,
                                   "Mask hash algorithm", level + 1);
            } else {
                SECU_Indent(out, level + 1);
                fprintf(out, "Invalid mask generation algorithm parameters\n");
            }
        }
        if (!param.saltLength.data) {
            SECU_Indent(out, level + 1);
            fprintf(out, "Salt length: default, %i (0x%2X)\n", 20, 20);
        } else {
            SECU_PrintInteger(out, ¶m.saltLength, "Salt length", level + 1);
        }
    } else {
        SECU_Indent(out, level + 1);
        fprintf(out, "Invalid RSA-PSS parameters\n");
    }
    PORT_FreeArena(pool, PR_FALSE);
}

void
secu_PrintKDF2Params(FILE *out, SECItem *value, char *m, int level)
{
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    SECStatus rv;
    secuPBEParams param;

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s:\n", m);
    }

    if (!pool) {
        SECU_Indent(out, level);
        fprintf(out, "Out of memory\n");
        return;
    }

    PORT_Memset(¶m, 0, sizeof param);
    rv = SEC_QuickDERDecodeItem(pool, ¶m, secuKDF2Params, value);
    if (rv == SECSuccess) {
        SECU_PrintAsHex(out, ¶m.salt, "Salt", level + 1);
        SECU_PrintInteger(out, ¶m.iterationCount, "Iteration Count",
                          level + 1);
        if (param.keyLength.data != NULL) {
            SECU_PrintInteger(out, ¶m.keyLength, "Key Length", level + 1);
        }
        if (param.kdfAlg.algorithm.data != NULL) {
            SECU_PrintAlgorithmID(out, ¶m.kdfAlg, "KDF algorithm", level + 1);
        } else {
            SECU_Indent(out, level + 1);
            fprintf(out, "Implicit KDF Algorithm: HMAC-SHA-1\n");
        }
    }
    PORT_FreeArena(pool, PR_FALSE);
}

void
secu_PrintPKCS5V2Params(FILE *out, SECItem *value, char *m, int level)
{
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    SECStatus rv;
    secuPBEParams param;

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s:\n", m);
    }

    if (!pool) {
        SECU_Indent(out, level);
        fprintf(out, "Out of memory\n");
        return;
    }

    PORT_Memset(¶m, 0, sizeof param);
    rv = SEC_QuickDERDecodeItem(pool, ¶m, secuPBEV2Params, value);
    if (rv == SECSuccess) {
        SECU_PrintAlgorithmID(out, ¶m.kdfAlg, "KDF", level + 1);
        SECU_PrintAlgorithmID(out, ¶m.cipherAlg, "Cipher", level + 1);
    }
    PORT_FreeArena(pool, PR_FALSE);
}

void
secu_PrintPBEParams(FILE *out, SECItem *value, char *m, int level)
{
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    SECStatus rv;
    secuPBEParams param;

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s:\n", m);
    }

    if (!pool) {
        SECU_Indent(out, level);
        fprintf(out, "Out of memory\n");
        return;
    }

    PORT_Memset(¶m, 0, sizeof(secuPBEParams));
    rv = SEC_QuickDERDecodeItem(pool, ¶m, secuPBEParamsTemp, value);
    if (rv == SECSuccess) {
        SECU_PrintAsHex(out, ¶m.salt, "Salt", level + 1);
        SECU_PrintInteger(out, ¶m.iterationCount, "Iteration Count",
                          level + 1);
    }
    PORT_FreeArena(pool, PR_FALSE);
}

/* This function does NOT expect a DER type and length. */
void
SECU_PrintAlgorithmID(FILE *out, SECAlgorithmID *a, char *m, int level)
{
    SECOidTag algtag;
    SECU_PrintObjectID(out, &a->algorithm, m, level);

    algtag = SECOID_GetAlgorithmTag(a);
    if (SEC_PKCS5IsAlgorithmPBEAlgTag(algtag)) {
        switch (algtag) {
            case SEC_OID_PKCS5_PBKDF2:
                secu_PrintKDF2Params(out, &a->parameters, "Parameters", level + 1);
                break;
            case SEC_OID_PKCS5_PBES2:
                secu_PrintPKCS5V2Params(out, &a->parameters, "Encryption", level + 1);
                break;
            case SEC_OID_PKCS5_PBMAC1:
                secu_PrintPKCS5V2Params(out, &a->parameters, "MAC", level + 1);
                break;
            default:
                secu_PrintPBEParams(out, &a->parameters, "Parameters", level + 1);
                break;
        }
        return;
    }

    if (a->parameters.len == 0 ||
        (a->parameters.len == 2 &&
         PORT_Memcmp(a->parameters.data, "\005\000", 2) == 0)) {
        /* No arguments or NULL argument */
    } else if (algtag == SEC_OID_PKCS1_RSA_PSS_SIGNATURE) {
        secu_PrintRSAPSSParams(out, &a->parameters, "Parameters", level + 1);
    } else {
        /* Print args to algorithm */
        SECU_PrintAsHex(out, &a->parameters, "Args", level + 1);
    }
}

static void
secu_PrintAttribute(FILE *out, SEC_PKCS7Attribute *attr, char *m, int level)
{
    SECItem *value;
    int i;
    char om[100];

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s:\n", m);
    }

    /*
     * Should make this smarter; look at the type field and then decode
     * and print the value(s) appropriately!
     */

    SECU_PrintObjectID(out, &(attr->type), "Type", level + 1);
    if (attr->values != NULL) {
        i = 0;
        while ((value = attr->values[i++]) != NULL) {
            snprintf(om, sizeof(om), "Value (%d)%s", i, attr->encoded ? " (encoded)" : "");
            if (attr->encoded || attr->typeTag == NULL) {
                SECU_PrintAny(out, value, om, level + 1);
            } else {
                switch (attr->typeTag->offset) {
                    default:
                        SECU_PrintAsHex(out, value, om, level + 1);
                        break;
                    case SEC_OID_PKCS9_CONTENT_TYPE:
                        SECU_PrintObjectID(out, value, om, level + 1);
                        break;
                    case SEC_OID_PKCS9_SIGNING_TIME:
                        SECU_PrintTimeChoice(out, value, om, level + 1);
                        break;
                }
            }
        }
    }
}

static void
secu_PrintECPublicKey(FILE *out, SECKEYPublicKey *pk, char *m, int level)
{
    SECItem curveOID = { siBuffer, NULL, 0 };

    SECU_Indent(out, level);
    fprintf(out, "%s:\n", m);
    SECU_PrintInteger(out, &pk->u.ec.publicValue, "PublicValue", level + 1);
    /* For named curves, the DEREncodedParams field contains an
     * ASN Object ID (0x06 is SEC_ASN1_OBJECT_ID).
     */

    if ((pk->u.ec.DEREncodedParams.len > 2) &&
        (pk->u.ec.DEREncodedParams.data[0] == 0x06)) {
        curveOID.len = pk->u.ec.DEREncodedParams.data[1];
        curveOID.data = pk->u.ec.DEREncodedParams.data + 2;
        curveOID.len = PR_MIN(curveOID.len, pk->u.ec.DEREncodedParams.len - 2);
        SECU_PrintObjectID(out, &curveOID, "Curve", level + 1);
    }
}

void
SECU_PrintRSAPublicKey(FILE *out, SECKEYPublicKey *pk, char *m, int level)
{
    SECU_Indent(out, level);
    fprintf(out, "%s:\n", m);
    SECU_PrintInteger(out, &pk->u.rsa.modulus, "Modulus", level + 1);
    SECU_PrintInteger(out, &pk->u.rsa.publicExponent, "Exponent", level + 1);
    if (pk->u.rsa.publicExponent.len == 1 &&
        pk->u.rsa.publicExponent.data[0] == 1) {
        SECU_Indent(out, level + 1);
        fprintf(out, "Error: INVALID RSA KEY!\n");
    }
}

void
SECU_PrintDSAPublicKey(FILE *out, SECKEYPublicKey *pk, char *m, int level)
{
    SECU_Indent(out, level);
    fprintf(out, "%s:\n", m);
    SECU_PrintInteger(out, &pk->u.dsa.params.prime, "Prime", level + 1);
    SECU_PrintInteger(out, &pk->u.dsa.params.subPrime, "Subprime", level + 1);
    SECU_PrintInteger(out, &pk->u.dsa.params.base, "Base", level + 1);
    SECU_PrintInteger(out, &pk->u.dsa.publicValue, "PublicValue", level + 1);
}

static void
secu_PrintSubjectPublicKeyInfo(FILE *out, PLArenaPool *arena,
                               CERTSubjectPublicKeyInfo *i, char *msg, int level)
{
    SECKEYPublicKey *pk;

    SECU_Indent(out, level);
    fprintf(out, "%s:\n", msg);
    SECU_PrintAlgorithmID(out, &i->algorithm, "Public Key Algorithm", level + 1);

    pk = SECKEY_ExtractPublicKey(i);
    if (pk) {
        switch (pk->keyType) {
            case rsaKey:
                SECU_PrintRSAPublicKey(out, pk, "RSA Public Key", level + 1);
                break;

            case dsaKey:
                SECU_PrintDSAPublicKey(out, pk, "DSA Public Key", level + 1);
                break;

            case ecKey:
                secu_PrintECPublicKey(out, pk, "EC Public Key", level + 1);
                break;

            case dhKey:
            case fortezzaKey:
            case keaKey:
                SECU_Indent(out, level);
                fprintf(out, "unable to format this SPKI algorithm type\n");
                goto loser;
            default:
                SECU_Indent(out, level);
                fprintf(out, "unknown SPKI algorithm type\n");
                goto loser;
        }
        PORT_FreeArena(pk->arena, PR_FALSE);
    } else {
        SECU_PrintErrMsg(out, level, "Error""Parsing public key");
    loser:
        if (i->subjectPublicKey.data) {
            SECItem tmp = i->subjectPublicKey;
            DER_ConvertBitString(&tmp);
            SECU_PrintAny(out, &tmp, "Raw", level);
        }
    }
}

static void
printStringWithoutCRLF(FILE *out, const char *str)
{
    const char *c = str;
    while (*c) {
        if (*c != '\r' && *c != '\n') {
            fputc(*c, out);
        }
        ++c;
    }
}

int
SECU_PrintDumpDerIssuerAndSerial(FILE *out, const SECItem *der, const char *m,
                                 int level)
{
    PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    CERTCertificate *c;
    int rv = SEC_ERROR_NO_MEMORY;
    char *derIssuerB64;
    char *derSerialB64;

    if (!arena)
        return rv;

    /* Decode certificate */
    c = PORT_ArenaZNew(arena, CERTCertificate);
    if (!c)
        goto loser;
    c->arena = arena;
    rv = SEC_ASN1DecodeItem(arena, c,
                            SEC_ASN1_GET(CERT_CertificateTemplate), der);
    if (rv) {
        SECU_PrintErrMsg(out, 0, "Error""Parsing extension");
        goto loser;
    }

    SECU_PrintName(out, &c->subject, "Subject", 0);
    if (!SECU_GetWrapEnabled()) /*SECU_PrintName didn't add newline*/
        SECU_Newline(out);
    SECU_PrintName(out, &c->issuer, "Issuer", 0);
    if (!SECU_GetWrapEnabled()) /*SECU_PrintName didn't add newline*/
        SECU_Newline(out);
    SECU_PrintInteger(out, &c->serialNumber, "Serial Number", 0);

    derIssuerB64 = BTOA_ConvertItemToAscii(&c->derIssuer);
    derSerialB64 = BTOA_ConvertItemToAscii(&c->serialNumber);

    fprintf(out, "Issuer DER Base64:\n");
    if (SECU_GetWrapEnabled()) {
        fprintf(out, "%s\n", derIssuerB64);
    } else {
        printStringWithoutCRLF(out, derIssuerB64);
        fputs("\n", out);
    }

    fprintf(out, "Serial DER Base64:\n");
    if (SECU_GetWrapEnabled()) {
        fprintf(out, "%s\n", derSerialB64);
    } else {
        printStringWithoutCRLF(out, derSerialB64);
        fputs("\n", out);
    }

    PORT_Free(derIssuerB64);
    PORT_Free(derSerialB64);

    fprintf(out, "Serial DER as C source: \n{ %d, \"", c->serialNumber.len);

    {
        unsigned int i;
        for (i = 0; i < c->serialNumber.len; ++i) {
            unsigned char *chardata = (unsigned char *)(c->serialNumber.data);
            unsigned char ch = *(chardata + i);

            fprintf(out, "\\x%02x", ch);
        }
        fprintf(out, "\" }\n");
    }

loser:
    PORT_FreeArena(arena, PR_FALSE);
    return rv;
}

static SECStatus
secu_PrintX509InvalidDate(FILE *out, SECItem *value, char *msg, int level)
{
    SECItem decodedValue;
    SECStatus rv;
    PRTime invalidTime;
    char *formattedTime = NULL;

    decodedValue.data = NULL;
    rv = SEC_ASN1DecodeItem(NULL, &decodedValue,
                            SEC_ASN1_GET(SEC_GeneralizedTimeTemplate),
                            value);
    if (rv == SECSuccess) {
        rv = DER_GeneralizedTimeToTime(&invalidTime, &decodedValue);
        if (rv == SECSuccess) {
            formattedTime = CERT_GenTime2FormattedAscii(invalidTime, "%a %b %d %H:%M:%S %Y");
            SECU_Indent(out, level + 1);
            fprintf(out, "%s: %s\n", msg, formattedTime);
            PORT_Free(formattedTime);
        }
    }
    PORT_Free(decodedValue.data);
    return (rv);
}

static SECStatus
PrintExtKeyUsageExtension(FILE *out, SECItem *value, char *msg, int level)
{
    CERTOidSequence *os;
    SECItem **op;

    os = CERT_DecodeOidSequence(value);
    if ((CERTOidSequence *)NULL == os) {
        return SECFailure;
    }

    for (op = os->oids; *op; op++) {
        SECU_PrintObjectID(out, *op, msg, level + 1);
    }
    CERT_DestroyOidSequence(os);
    return SECSuccess;
}

static SECStatus
secu_PrintBasicConstraints(FILE *out, SECItem *value, char *msg, int level)
{
    CERTBasicConstraints constraints;
    SECStatus rv;

    SECU_Indent(out, level);
    if (msg) {
        fprintf(out, "%s: ", msg);
    }
    rv = CERT_DecodeBasicConstraintValue(&constraints, value);
    if (rv == SECSuccess && constraints.isCA) {
        if (constraints.pathLenConstraint >= 0) {
            fprintf(out, "Is a CA with a maximum path length of %d.\n",
                    constraints.pathLenConstraint);
        } else {
            fprintf(out, "Is a CA with no maximum path length.\n");
        }
    } else {
        fprintf(out, "Is not a CA.\n");
    }
    return SECSuccess;
}

static const char *const nsTypeBits[] = {
    "SSL Client",
    "SSL Server",
    "S/MIME",
    "Object Signing",
    "Reserved",
    "SSL CA",
    "S/MIME CA",
    "ObjectSigning CA"
};

/* NSCertType is merely a bit string whose bits are displayed symbolically */
static SECStatus
secu_PrintNSCertType(FILE *out, SECItem *value, char *msg, int level)
{
    int unused;
    int NS_Type;
    int i;
    int found = 0;
    SECItem my = *value;

    if ((my.data[0] != SEC_ASN1_BIT_STRING) ||
        SECSuccess != SECU_StripTagAndLength(&my)) {
        SECU_PrintAny(out, value, "Data", level);
        return SECSuccess;
    }

    unused = (my.len == 2) ? (my.data[0] & 0x0f) : 0;
    NS_Type = my.data[1] & (0xff << unused);

    SECU_Indent(out, level);
    if (msg) {
        fprintf(out, "%s: ", msg);
    } else {
        fprintf(out, "Netscape Certificate Type: ");
    }
    for (i = 0; i < 8; i++) {
        if ((0x80 >> i) & NS_Type) {
            fprintf(out, "%c%s", (found ? ',' : '<'), nsTypeBits[i]);
            found = 1;
        }
    }
    fprintf(out, (found ? ">\n" : "none\n"));
    return SECSuccess;
}

static const char *const usageBits[] = {
    "Digital Signature",   /* 0x80 */
    "Non-Repudiation",     /* 0x40 */
    "Key Encipherment",    /* 0x20 */
    "Data Encipherment",   /* 0x10 */
    "Key Agreement",       /* 0x08 */
    "Certificate Signing"/* 0x04 */
    "CRL Signing",         /* 0x02 */
    "Encipher Only",       /* 0x01 */
    "Decipher Only",       /* 0x0080 */
    NULL
};

/* X509KeyUsage is merely a bit string whose bits are displayed symbolically */
static void
secu_PrintX509KeyUsage(FILE *out, SECItem *value, char *msg, int level)
{
    int unused;
    int usage;
    int i;
    int found = 0;
    SECItem my = *value;

    if ((my.data[0] != SEC_ASN1_BIT_STRING) ||
        SECSuccess != SECU_StripTagAndLength(&my)) {
        SECU_PrintAny(out, value, "Data", level);
        return;
    }

    unused = (my.len >= 2) ? (my.data[0] & 0x0f) : 0;
    usage = (my.len == 2) ? (my.data[1] & (0xff << unused)) << 8
                          : (my.data[1] << 8) |
                                (my.data[2] & (0xff << unused));

    SECU_Indent(out, level);
    fprintf(out, "Usages: ");
    for (i = 0; usageBits[i]; i++) {
        if ((0x8000 >> i) & usage) {
            if (found)
                SECU_Indent(out, level + 2);
            fprintf(out, "%s\n", usageBits[i]);
            found = 1;
        }
    }
    if (!found) {
        fprintf(out, "(none)\n");
    }
}

static void
secu_PrintIPAddress(FILE *out, SECItem *value, char *msg, int level)
{
    PRStatus st;
    PRNetAddr addr;
    char addrBuf[80];

    memset(&addr, 0, sizeof addr);
    if (value->len == 4) {
        addr.inet.family = PR_AF_INET;
        memcpy(&addr.inet.ip, value->data, value->len);
    } else if (value->len == 16) {
        addr.ipv6.family = PR_AF_INET6;
        memcpy(addr.ipv6.ip.pr_s6_addr, value->data, value->len);
        if (PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped)) {
            /* convert to IPv4.  */
            addr.inet.family = PR_AF_INET;
            memcpy(&addr.inet.ip, &addr.ipv6.ip.pr_s6_addr[12], 4);
            memset(&addr.inet.pad[0], 0, sizeof addr.inet.pad);
        }
    } else {
        goto loser;
    }

    st = PR_NetAddrToString(&addr, addrBuf, sizeof addrBuf);
    if (st == PR_SUCCESS) {
        SECU_Indent(out, level);
        fprintf(out, "%s: %s\n", msg, addrBuf);
    } else {
    loser:
        SECU_PrintAsHex(out, value, msg, level);
    }
}

static void
secu_PrintGeneralName(FILE *out, CERTGeneralName *gname, char *msg, int level)
{
    char label[40];
    if (msg && msg[0]) {
        SECU_Indent(out, level++);
        fprintf(out, "%s: \n", msg);
    }
    switch (gname->type) {
        case certOtherName:
            SECU_PrintAny(out, &gname->name.OthName.name, "Other Name", level);
            SECU_PrintObjectID(out, &gname->name.OthName.oid, "OID", level + 1);
            break;
        case certDirectoryName:
            SECU_PrintName(out, &gname->name.directoryName, "Directory Name", level);
            break;
        case certRFC822Name:
            secu_PrintRawString(out, &gname->name.other, "RFC822 Name", level);
            break;
        case certDNSName:
            secu_PrintRawString(out, &gname->name.other, "DNS name", level);
            break;
        case certURI:
            secu_PrintRawString(out, &gname->name.other, "URI", level);
            break;
        case certIPAddress:
            secu_PrintIPAddress(out, &gname->name.other, "IP Address", level);
            break;
        case certRegisterID:
            SECU_PrintObjectID(out, &gname->name.other, "Registered ID", level);
            break;
        case certX400Address:
            SECU_PrintAny(out, &gname->name.other, "X400 Address", level);
            break;
        case certEDIPartyName:
            SECU_PrintAny(out, &gname->name.other, "EDI Party", level);
            break;
        default:
            PR_snprintf(label, sizeof label, "unknown type [%d]",
                        (int)gname->type - 1);
            SECU_PrintAsHex(out, &gname->name.other, label, level);
            break;
    }
}

static void
secu_PrintGeneralNames(FILE *out, CERTGeneralName *gname, char *msg, int level)
{
    CERTGeneralName *name = gname;
    do {
        secu_PrintGeneralName(out, name, msg, level);
        name = CERT_GetNextGeneralName(name);
    } while (name && name != gname);
}

static void
secu_PrintAuthKeyIDExtension(FILE *out, SECItem *value, char *msg, int level)
{
    CERTAuthKeyID *kid = NULL;
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!pool) {
        SECU_PrintError("Error""Allocating new ArenaPool");
        return;
    }
    kid = CERT_DecodeAuthKeyID(pool, value);
    if (!kid) {
        SECU_PrintErrMsg(out, level, "Error""Parsing extension");
        SECU_PrintAny(out, value, "Data", level);
    } else {
        int keyIDPresent = (kid->keyID.data && kid->keyID.len);
        int issuerPresent = kid->authCertIssuer != NULL;
        int snPresent = (kid->authCertSerialNumber.data &&
                         kid->authCertSerialNumber.len);

        if (keyIDPresent)
            SECU_PrintAsHex(out, &kid->keyID, "Key ID", level);
        if (issuerPresent)
            secu_PrintGeneralName(out, kid->authCertIssuer, "Issuer", level);
        if (snPresent)
            SECU_PrintInteger(out, &kid->authCertSerialNumber,
                              "Serial Number", level);
    }
    PORT_FreeArena(pool, PR_FALSE);
}

static void
secu_PrintAltNameExtension(FILE *out, SECItem *value, char *msg, int level)
{
    CERTGeneralName *nameList;
    CERTGeneralName *current;
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!pool) {
        SECU_PrintError("Error""Allocating new ArenaPool");
        return;
    }
    nameList = current = CERT_DecodeAltNameExtension(pool, value);
    if (!current) {
        if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) {
            /* Decoder found empty sequence, which is invalid. */
            PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID);
        }
        SECU_PrintErrMsg(out, level, "Error""Parsing extension");
        SECU_PrintAny(out, value, "Data", level);
    } else {
        do {
            secu_PrintGeneralName(out, current, msg, level);
            current = CERT_GetNextGeneralName(current);
        } while (current != nameList);
    }
    PORT_FreeArena(pool, PR_FALSE);
}

static void
secu_PrintCRLDistPtsExtension(FILE *out, SECItem *value, char *msg, int level)
{
    CERTCrlDistributionPoints *dPoints;
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!pool) {
        SECU_PrintError("Error""Allocating new ArenaPool");
        return;
    }
    dPoints = CERT_DecodeCRLDistributionPoints(pool, value);
    if (dPoints && dPoints->distPoints && dPoints->distPoints[0]) {
        CRLDistributionPoint **pPoints = dPoints->distPoints;
        CRLDistributionPoint *pPoint;
        while (NULL != (pPoint = *pPoints++)) {
            SECU_Indent(out, level);
            fputs("Distribution point:\n", out);
            if (pPoint->distPointType == generalName &&
                pPoint->distPoint.fullName != NULL) {
                secu_PrintGeneralNames(out, pPoint->distPoint.fullName, NULL,
                                       level + 1);
            } else if (pPoint->distPointType == relativeDistinguishedName &&
                       pPoint->distPoint.relativeName.avas) {
                SECU_PrintRDN(out, &pPoint->distPoint.relativeName, "RDN",
                              level + 1);
            } else if (pPoint->derDistPoint.data) {
                SECU_PrintAny(out, &pPoint->derDistPoint, "Point", level + 1);
            }
            if (pPoint->reasons.data) {
                secu_PrintDecodedBitString(out, &pPoint->reasons, "Reasons",
                                           level + 1);
            }
            if (pPoint->crlIssuer) {
                secu_PrintGeneralName(out, pPoint->crlIssuer, "CRL issuer",
                                      level + 1);
            }
        }
    } else {
        SECU_PrintErrMsg(out, level, "Error""Parsing extension");
        SECU_PrintAny(out, value, "Data", level);
    }
    PORT_FreeArena(pool, PR_FALSE);
}

static void
secu_PrintNameConstraintSubtree(FILE *out, CERTNameConstraint *value,
                                char *msg, int level)
{
    CERTNameConstraint *head = value;
    SECU_Indent(out, level);
    fprintf(out, "%s Subtree:\n", msg);
    level++;
    do {
        secu_PrintGeneralName(out, &value->name, NULL, level);
        if (value->min.data)
            SECU_PrintInteger(out, &value->min, "Minimum", level + 1);
        if (value->max.data)
            SECU_PrintInteger(out, &value->max, "Maximum", level + 1);
        value = CERT_GetNextNameConstraint(value);
    } while (value != head);
}

static void
secu_PrintNameConstraintsExtension(FILE *out, SECItem *value, char *msg, int level)
{
    CERTNameConstraints *cnstrnts;
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!pool) {
        SECU_PrintError("Error""Allocating new ArenaPool");
        return;
    }
    cnstrnts = CERT_DecodeNameConstraintsExtension(pool, value);
    if (!cnstrnts) {
        SECU_PrintErrMsg(out, level, "Error""Parsing extension");
        SECU_PrintAny(out, value, "Raw", level);
    } else {
        if (cnstrnts->permited)
            secu_PrintNameConstraintSubtree(out, cnstrnts->permited,
                                            "Permitted", level);
        if (cnstrnts->excluded)
            secu_PrintNameConstraintSubtree(out, cnstrnts->excluded,
                                            "Excluded", level);
    }
    PORT_FreeArena(pool, PR_FALSE);
}

static void
secu_PrintAuthorityInfoAcess(FILE *out, SECItem *value, char *msg, int level)
{
    CERTAuthInfoAccess **infos = NULL;
    PLArenaPool *pool = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!pool) {
        SECU_PrintError("Error""Allocating new ArenaPool");
        return;
    }
    infos = CERT_DecodeAuthInfoAccessExtension(pool, value);
    if (!infos) {
        SECU_PrintErrMsg(out, level, "Error""Parsing extension");
        SECU_PrintAny(out, value, "Raw", level);
    } else {
        CERTAuthInfoAccess *info;
        while (NULL != (info = *infos++)) {
            if (info->method.data) {
                SECU_PrintObjectID(out, &info->method, "Method", level);
            } else {
                SECU_Indent(out, level);
                fprintf(out, "Error: missing method\n");
            }
            if (info->location) {
                secu_PrintGeneralName(out, info->location, "Location", level);
            } else {
                SECU_PrintAny(out, &info->derLocation, "Location", level);
            }
        }
    }
    PORT_FreeArena(pool, PR_FALSE);
}

void
SECU_PrintExtensions(FILE *out, CERTCertExtension **extensions,
                     char *msg, int level)
{
    SECOidTag oidTag;

    if (extensions) {
        if (msg && *msg) {
            SECU_Indent(out, level++);
            fprintf(out, "%s:\n", msg);
        }

        while (*extensions) {
            SECItem *tmpitem;

            tmpitem = &(*extensions)->id;
            SECU_PrintObjectID(out, tmpitem, "Name", level);

            tmpitem = &(*extensions)->critical;
            if (tmpitem->len) {
                secu_PrintBoolean(out, tmpitem, "Critical", level);
            }

            oidTag = SECOID_FindOIDTag(&((*extensions)->id));
            tmpitem = &((*extensions)->value);

            switch (oidTag) {
                case SEC_OID_X509_INVALID_DATE:
                case SEC_OID_NS_CERT_EXT_CERT_RENEWAL_TIME:
                    secu_PrintX509InvalidDate(out, tmpitem, "Date", level);
                    break;
                case SEC_OID_X509_CERTIFICATE_POLICIES:
                    SECU_PrintPolicy(out, tmpitem, "Data", level);
                    break;
                case SEC_OID_NS_CERT_EXT_BASE_URL:
                case SEC_OID_NS_CERT_EXT_REVOCATION_URL:
                case SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL:
                case SEC_OID_NS_CERT_EXT_CA_CRL_URL:
                case SEC_OID_NS_CERT_EXT_CA_CERT_URL:
                case SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL:
                case SEC_OID_NS_CERT_EXT_CA_POLICY_URL:
                case SEC_OID_NS_CERT_EXT_HOMEPAGE_URL:
                case SEC_OID_NS_CERT_EXT_LOST_PASSWORD_URL:
                case SEC_OID_OCSP_RESPONDER:
                    SECU_PrintString(out, tmpitem, "URL", level);
                    break;
                case SEC_OID_NS_CERT_EXT_COMMENT:
                    SECU_PrintString(out, tmpitem, "Comment", level);
                    break;
                case SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME:
                    SECU_PrintString(out, tmpitem, "ServerName", level);
                    break;
                case SEC_OID_NS_CERT_EXT_CERT_TYPE:
                    secu_PrintNSCertType(out, tmpitem, "Data", level);
                    break;
                case SEC_OID_X509_BASIC_CONSTRAINTS:
                    secu_PrintBasicConstraints(out, tmpitem, "Data", level);
                    break;
                case SEC_OID_X509_EXT_KEY_USAGE:
                    PrintExtKeyUsageExtension(out, tmpitem, NULL, level);
                    break;
                case SEC_OID_X509_KEY_USAGE:
                    secu_PrintX509KeyUsage(out, tmpitem, NULL, level);
                    break;
                case SEC_OID_X509_AUTH_KEY_ID:
                    secu_PrintAuthKeyIDExtension(out, tmpitem, NULL, level);
                    break;
                case SEC_OID_X509_SUBJECT_ALT_NAME:
                case SEC_OID_X509_ISSUER_ALT_NAME:
                    secu_PrintAltNameExtension(out, tmpitem, NULL, level);
                    break;
                case SEC_OID_X509_CRL_DIST_POINTS:
                    secu_PrintCRLDistPtsExtension(out, tmpitem, NULL, level);
                    break;
                case SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD:
                    SECU_PrintPrivKeyUsagePeriodExtension(out, tmpitem, NULL,
                                                          level);
                    break;
                case SEC_OID_X509_NAME_CONSTRAINTS:
                    secu_PrintNameConstraintsExtension(out, tmpitem, NULL, level);
                    break;
                case SEC_OID_X509_AUTH_INFO_ACCESS:
                    secu_PrintAuthorityInfoAcess(out, tmpitem, NULL, level);
                    break;

                case SEC_OID_X509_CRL_NUMBER:
                case SEC_OID_X509_REASON_CODE:

                /* PKIX OIDs */
                case SEC_OID_PKIX_OCSP:
                case SEC_OID_PKIX_OCSP_BASIC_RESPONSE:
                case SEC_OID_PKIX_OCSP_NONCE:
                case SEC_OID_PKIX_OCSP_CRL:
                case SEC_OID_PKIX_OCSP_RESPONSE:
                case SEC_OID_PKIX_OCSP_NO_CHECK:
                case SEC_OID_PKIX_OCSP_ARCHIVE_CUTOFF:
                case SEC_OID_PKIX_OCSP_SERVICE_LOCATOR:
                case SEC_OID_PKIX_REGCTRL_REGTOKEN:
                case SEC_OID_PKIX_REGCTRL_AUTHENTICATOR:
                case SEC_OID_PKIX_REGCTRL_PKIPUBINFO:
                case SEC_OID_PKIX_REGCTRL_PKI_ARCH_OPTIONS:
                case SEC_OID_PKIX_REGCTRL_OLD_CERT_ID:
                case SEC_OID_PKIX_REGCTRL_PROTOCOL_ENC_KEY:
                case SEC_OID_PKIX_REGINFO_UTF8_PAIRS:
                case SEC_OID_PKIX_REGINFO_CERT_REQUEST:

                /* Netscape extension OIDs. */
                case SEC_OID_NS_CERT_EXT_NETSCAPE_OK:
                case SEC_OID_NS_CERT_EXT_ISSUER_LOGO:
                case SEC_OID_NS_CERT_EXT_SUBJECT_LOGO:
                case SEC_OID_NS_CERT_EXT_ENTITY_LOGO:
                case SEC_OID_NS_CERT_EXT_USER_PICTURE:

                /* x.509 v3 Extensions */
                case SEC_OID_X509_SUBJECT_DIRECTORY_ATTR:
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=92 G=94

¤ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge