Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/security/nss/lib/pkcs12/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 110 kB image not shown  

Quelle  p12d.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/. */


#include "nssrenam.h"
#include "nss.h"
#include "p12t.h"
#include "p12.h"
#include "plarena.h"
#include "secitem.h"
#include "secoid.h"
#include "seccomon.h"
#include "secport.h"
#include "cert.h"
#include "secpkcs7.h"
#include "secasn1.h"
#include "secerr.h"
#include "pk11func.h"
#include "p12plcy.h"
#include "p12local.h"
#include "secder.h"
#include "secport.h"

#include "certdb.h"

#include "prcpucfg.h"

/* This belongs in secport.h */
#define PORT_ArenaGrowArray(poolp, oldptr, type, oldnum, newnum) \
    (type *)PORT_ArenaGrow((poolp), (oldptr),                    \
                           (oldnum) * sizeof(type), (newnum) * sizeof(type))

typedef struct sec_PKCS12SafeContentsContextStr sec_PKCS12SafeContentsContext;

/* Opaque structure for decoding SafeContents.  These are used
 * for each authenticated safe as well as any nested safe contents.
 */

struct sec_PKCS12SafeContentsContextStr {
    /* the parent decoder context */
    SEC_PKCS12DecoderContext *p12dcx;

    /* memory arena to allocate space from */
    PLArenaPool *arena;

    /* decoder context and destination for decoding safe contents */
    SEC_ASN1DecoderContext *safeContentsA1Dcx;
    sec_PKCS12SafeContents safeContents;

    /* information for decoding safe bags within the safe contents.
     * these variables are updated for each safe bag decoded.
     */

    SEC_ASN1DecoderContext *currentSafeBagA1Dcx;
    sec_PKCS12SafeBag *currentSafeBag;
    PRBool skipCurrentSafeBag;

    /* if the safe contents is nested, the parent is pointed to here. */
    sec_PKCS12SafeContentsContext *nestedSafeContentsCtx;
};

/* opaque decoder context structure.  information for decoding a pkcs 12
 * PDU are stored here as well as decoding pointers for intermediary
 * structures which are part of the PKCS 12 PDU.  Upon a successful
 * decode, the safe bags containing certificates and keys encountered.
 */

struct SEC_PKCS12DecoderContextStr {
    PLArenaPool *arena;
    PK11SlotInfo *slot;
    void *wincx;
    PRBool error;
    int errorValue;

    /* password */
    SECItem *pwitem;

    /* used for decoding the PFX structure */
    SEC_ASN1DecoderContext *pfxA1Dcx;
    sec_PKCS12PFXItem pfx;

    /* safe bags found during decoding */
    sec_PKCS12SafeBag **safeBags;
    unsigned int safeBagCount;

    /* state variables for decoding authenticated safes. */
    SEC_PKCS7DecoderContext *currentASafeP7Dcx;
    SEC_ASN1DecoderContext *aSafeA1Dcx;
    SEC_PKCS7DecoderContext *aSafeP7Dcx;
    SEC_PKCS7ContentInfo *aSafeCinfo;
    sec_PKCS12AuthenticatedSafe authSafe;
    sec_PKCS12SafeContents safeContents;

    /* safe contents info */
    unsigned int safeContentsCnt;
    sec_PKCS12SafeContentsContext **safeContentsList;

    /* HMAC info */
    sec_PKCS12MacData macData;

    /* routines for reading back the data to be hmac'd */
    /* They are called as follows.
     *
     * Stage 1: decode the aSafes cinfo into a buffer in dArg,
     * which p12d.c sometimes refers to as the "temp file".
     * This occurs during SEC_PKCS12DecoderUpdate calls.
     *
     * dOpen(dArg, PR_FALSE)
     * dWrite(dArg, buf, len)
     * ...
     * dWrite(dArg, buf, len)
     * dClose(dArg, PR_FALSE)
     *
     * Stage 2: verify MAC
     * This occurs SEC_PKCS12DecoderVerify.
     *
     * dOpen(dArg, PR_TRUE)
     * dRead(dArg, buf, IN_BUF_LEN)
     * ...
     * dRead(dArg, buf, IN_BUF_LEN)
     * dClose(dArg, PR_TRUE)
     */

    digestOpenFn dOpen;
    digestCloseFn dClose;
    digestIOFn dRead, dWrite;
    void *dArg;
    PRBool dIsOpen; /* is the temp file created? */

    /* helper functions */
    SECKEYGetPasswordKey pwfn;
    void *pwfnarg;
    PRBool swapUnicodeBytes;
    PRBool forceUnicode;

    /* import information */
    PRBool bagsVerified;

    /* buffer management for the default callbacks implementation */
    void *buffer;       /* storage area */
    PRInt32 filesize;   /* actual data size */
    PRInt32 allocated;  /* total buffer size allocated */
    PRInt32 currentpos; /* position counter */
    SECPKCS12TargetTokenCAs tokenCAs;
    sec_PKCS12SafeBag **keyList; /* used by ...IterateNext() */
    unsigned int iteration;
    SEC_PKCS12DecoderItem decitem;
};

/* forward declarations of functions that are used when decoding
 * safeContents bags which are nested and when decoding the
 * authenticatedSafes.
 */

static SECStatus
sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                  *safeContentsCtx);
static SECStatus
sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                   *safeContentsCtx);

/* make sure that the PFX version being decoded is a version
 * which we support.
 */

static PRBool
sec_pkcs12_proper_version(sec_PKCS12PFXItem *pfx)
{
    /* if no version, assume it is not supported */
    if (pfx->version.len == 0) {
        return PR_FALSE;
    }

    if (DER_GetInteger(&pfx->version) > SEC_PKCS12_VERSION) {
        return PR_FALSE;
    }

    return PR_TRUE;
}

/* retrieve the key for decrypting the safe contents */
static PK11SymKey *
sec_pkcs12_decoder_get_decrypt_key(void *arg, SECAlgorithmID *algid)
{
    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;
    PK11SlotInfo *slot;
    PK11SymKey *bulkKey;
    SECItem pwitem = { 0 };
    SECOidTag algorithm;

    if (!p12dcx) {
        return NULL;
    }

    /* if no slot specified, use the internal key slot */
    if (p12dcx->slot) {
        slot = PK11_ReferenceSlot(p12dcx->slot);
    } else {
        slot = PK11_GetInternalKeySlot();
    }

    algorithm = SECOID_GetAlgorithmTag(algid);

    if (p12dcx->forceUnicode) {
        if (SECITEM_CopyItem(NULL, &pwitem, p12dcx->pwitem) != SECSuccess) {
            PK11_FreeSlot(slot);
            return NULL;
        }
    } else {
        if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, p12dcx->pwitem)) {
            PK11_FreeSlot(slot);
            return NULL;
        }
    }

    bulkKey = PK11_PBEKeyGen(slot, algid, &pwitem, PR_FALSE, p12dcx->wincx);
    /* some tokens can't generate PBE keys on their own, generate the
     * key in the internal slot, and let the Import code deal with it,
     * (if the slot can't generate PBEs, then we need to use the internal
     * slot anyway to unwrap). */

    if (!bulkKey && !PK11_IsInternal(slot)) {
        PK11_FreeSlot(slot);
        slot = PK11_GetInternalKeySlot();
        bulkKey = PK11_PBEKeyGen(slot, algid, &pwitem, PR_FALSE, p12dcx->wincx);
    }
    PK11_FreeSlot(slot);

    /* set the password data on the key */
    if (bulkKey) {
        PK11_SetSymKeyUserData(bulkKey, p12dcx->pwitem, NULL);
    }

    if (pwitem.data) {
        SECITEM_ZfreeItem(&pwitem, PR_FALSE);
    }

    return bulkKey;
}

/* XXX this needs to be modified to handle enveloped data.  most
 * likely, it should mirror the routines for SMIME in that regard.
 */

static PRBool
sec_pkcs12_decoder_decryption_allowed(SECAlgorithmID *algid,
                                      PK11SymKey *bulkkey)
{
    PRBool decryptionAllowed = SEC_PKCS12DecryptionAllowed(algid);

    if (!decryptionAllowed) {
        PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM);
        return PR_FALSE;
    }

    return PR_TRUE;
}

/* when we encounter a new safe bag during the decoding, we need
 * to allocate space for the bag to be decoded to and set the
 * state variables appropriately.  all of the safe bags are allocated
 * in a buffer in the outer SEC_PKCS12DecoderContext, however,
 * a pointer to the safeBag is also used in the sec_PKCS12SafeContentsContext
 * for the current bag.
 */

static SECStatus
sec_pkcs12_decoder_init_new_safe_bag(sec_PKCS12SafeContentsContext
                                         *safeContentsCtx)
{
    void *mark = NULL;
    SEC_PKCS12DecoderContext *p12dcx;

    /* make sure that the structures are defined, and there has
     * not been an error in the decoding
     */

    if (!safeContentsCtx || !safeContentsCtx->p12dcx || safeContentsCtx->p12dcx->error) {
        return SECFailure;
    }

    p12dcx = safeContentsCtx->p12dcx;
    mark = PORT_ArenaMark(p12dcx->arena);

    /* allocate a new safe bag, if bags already exist, grow the
     * list of bags, otherwise allocate a new list.  the list is
     * NULL terminated.
     */

    p12dcx->safeBags = (!p12dcx->safeBagCount)
                           ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, 2)
                           : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeBags,
                                                 sec_PKCS12SafeBag *, p12dcx->safeBagCount + 1,
                                                 p12dcx->safeBagCount + 2);

    if (!p12dcx->safeBags) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* append the bag to the end of the list and update the reference
     * in the safeContentsCtx.
     */

    p12dcx->safeBags[p12dcx->safeBagCount] =
        safeContentsCtx->currentSafeBag =
            PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag);
    if (!safeContentsCtx->currentSafeBag) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }
    p12dcx->safeBags[++p12dcx->safeBagCount] = NULL;

    safeContentsCtx->currentSafeBag->slot = safeContentsCtx->p12dcx->slot;
    safeContentsCtx->currentSafeBag->pwitem = safeContentsCtx->p12dcx->pwitem;
    safeContentsCtx->currentSafeBag->swapUnicodeBytes =
        safeContentsCtx->p12dcx->swapUnicodeBytes;
    safeContentsCtx->currentSafeBag->arena = safeContentsCtx->p12dcx->arena;
    safeContentsCtx->currentSafeBag->tokenCAs =
        safeContentsCtx->p12dcx->tokenCAs;

    PORT_ArenaUnmark(p12dcx->arena, mark);
    return SECSuccess;

loser:

    /* if an error occurred, release the memory and set the error flag
     * the only possible errors triggered by this function are memory
     * related.
     */

    if (mark) {
        PORT_ArenaRelease(p12dcx->arena, mark);
    }

    p12dcx->error = PR_TRUE;
    return SECFailure;
}

/* A wrapper for updating the ASN1 context in which a safeBag is
 * being decoded.  This function is called as a callback from
 * secasn1d when decoding SafeContents structures.
 */

static void
sec_pkcs12_decoder_safe_bag_update(void *arg, const char *data,
                                   unsigned long len, int depth,
                                   SEC_ASN1EncodingPart data_kind)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    if (!safeContentsCtx || !safeContentsCtx->p12dcx || !safeContentsCtx->currentSafeBagA1Dcx) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* make sure that there are no errors and we are not skipping the current safeBag */
    if (p12dcx->error || safeContentsCtx->skipCurrentSafeBag) {
        goto loser;
    }

    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->currentSafeBagA1Dcx, data, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        p12dcx->error = PR_TRUE;
        goto loser;
    }

    /* The update may have set safeContentsCtx->skipCurrentSafeBag, and we
     * may not get another opportunity to clean up the decoder context.
     */

    if (safeContentsCtx->skipCurrentSafeBag) {
        goto loser;
    }

    return;

loser:
    /* Finish the decoder context. Because there
     * is not a way of returning an error message, it may be worth
     * while to do a check higher up and finish any decoding contexts
     * that are still open.
     */

    SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx);
    safeContentsCtx->currentSafeBagA1Dcx = NULL;
    return;
}

/* notify function for decoding safeBags.  This function is
 * used to filter safeBag types which are not supported,
 * initiate the decoding of nested safe contents, and decode
 * safeBags in general.  this function is set when the decoder
 * context for the safeBag is first created.
 */

static void
sec_pkcs12_decoder_safe_bag_notify(void *arg, PRBool before,
                                   void *dest, int real_depth)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    sec_PKCS12SafeBag *bag;
    PRBool after;

    /* if an error is encountered, return */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* to make things more readable */
    if (before)
        after = PR_FALSE;
    else
        after = PR_TRUE;

    /* have we determined the safeBagType yet? */
    bag = safeContentsCtx->currentSafeBag;
    if (bag->bagTypeTag == NULL) {
        if (after && (dest == &(bag->safeBagType))) {
            bag->bagTypeTag = SECOID_FindOID(&(bag->safeBagType));
            if (bag->bagTypeTag == NULL) {
                p12dcx->error = PR_TRUE;
                p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
            }
        }
        return;
    }

    /* process the safeBag depending on it's type.  those
     * which we do not support, are ignored.  we start a decoding
     * context for a nested safeContents.
     */

    switch (bag->bagTypeTag->offset) {
        case SEC_OID_PKCS12_V1_KEY_BAG_ID:
        case SEC_OID_PKCS12_V1_CERT_BAG_ID:
        case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
            break;
        case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID:
            /* if we are just starting to decode the safeContents, initialize
             * a new safeContentsCtx to process it.
             */

            if (before && (dest == &(bag->safeBagContent))) {
                sec_pkcs12_decoder_begin_nested_safe_contents(safeContentsCtx);
            } else if (after && (dest == &(bag->safeBagContent))) {
                /* clean up the nested decoding */
                sec_pkcs12_decoder_finish_nested_safe_contents(safeContentsCtx);
            }
            break;
        case SEC_OID_PKCS12_V1_CRL_BAG_ID:
        case SEC_OID_PKCS12_V1_SECRET_BAG_ID:
        default:
            /* skip any safe bag types we don't understand or handle */
            safeContentsCtx->skipCurrentSafeBag = PR_TRUE;
            break;
    }

    return;
}

/* notify function for decoding safe contents.  each entry in the
 * safe contents is a safeBag which needs to be allocated and
 * the decoding context initialized at the beginning and then
 * the context needs to be closed and finished at the end.
 *
 * this function is set when the safeContents decode context is
 * initialized.
 */

static void
sec_pkcs12_decoder_safe_contents_notify(void *arg, PRBool before,
                                        void *dest, int real_depth)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    /* if there is an error we don't want to continue processing,
     * just return and keep going.
     */

    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* if we are done with the current safeBag, then we need to
     * finish the context and set the state variables appropriately.
     */

    if (!before) {
        SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsA1Dcx);
        SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx);
        safeContentsCtx->currentSafeBagA1Dcx = NULL;
        safeContentsCtx->skipCurrentSafeBag = PR_FALSE;
    } else {
        /* we are starting a new safe bag.  we need to allocate space
         * for the bag and initialize the decoding context.
         */

        rv = sec_pkcs12_decoder_init_new_safe_bag(safeContentsCtx);
        if (rv != SECSuccess) {
            goto loser;
        }

        /* set up the decoder context */
        safeContentsCtx->currentSafeBagA1Dcx =
            SEC_ASN1DecoderStart(p12dcx->arena,
                                 safeContentsCtx->currentSafeBag,
                                 sec_PKCS12SafeBagTemplate);
        if (!safeContentsCtx->currentSafeBagA1Dcx) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }

        /* set the notify and filter procs so that the safe bag
         * data gets sent to the proper location when decoding.
         */

        SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->currentSafeBagA1Dcx,
                                     sec_pkcs12_decoder_safe_bag_notify,
                                     safeContentsCtx);
        SEC_ASN1DecoderSetFilterProc(safeContentsCtx->safeContentsA1Dcx,
                                     sec_pkcs12_decoder_safe_bag_update,
                                     safeContentsCtx, PR_TRUE);
    }

    return;

loser:
    /* in the event of an error, we want to close the decoding
     * context and clear the filter and notify procedures.
     */

    p12dcx->error = PR_TRUE;

    if (safeContentsCtx->currentSafeBagA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx);
        safeContentsCtx->currentSafeBagA1Dcx = NULL;
    }

    SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->safeContentsA1Dcx);
    SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsA1Dcx);

    return;
}

/* initialize the safeContents for decoding.  this routine
 * is used for authenticatedSafes as well as nested safeContents.
 */

static sec_PKCS12SafeContentsContext *
sec_pkcs12_decoder_safe_contents_init_decode(SEC_PKCS12DecoderContext *p12dcx,
                                             PRBool nestedSafe)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx = NULL;
    const SEC_ASN1Template *theTemplate;

    if (!p12dcx || p12dcx->error) {
        return NULL;
    }

    /* allocate a new safeContents list or grow the existing list and
     * append the new safeContents onto the end.
     */

    p12dcx->safeContentsList = (!p12dcx->safeContentsCnt)
                                   ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeContentsContext *, 2)
                                   : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeContentsList,
                                                         sec_PKCS12SafeContentsContext *,
                                                         1 + p12dcx->safeContentsCnt,
                                                         2 + p12dcx->safeContentsCnt);

    if (!p12dcx->safeContentsList) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    p12dcx->safeContentsList[p12dcx->safeContentsCnt] = safeContentsCtx =
        PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeContentsContext);
    if (!p12dcx->safeContentsList[p12dcx->safeContentsCnt]) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }
    p12dcx->safeContentsList[++p12dcx->safeContentsCnt] = NULL;

    /* set up the state variables */
    safeContentsCtx->p12dcx = p12dcx;
    safeContentsCtx->arena = p12dcx->arena;

    /* begin the decoding -- the template is based on whether we are
     * decoding a nested safeContents or not.
     */

    if (nestedSafe == PR_TRUE) {
        theTemplate = sec_PKCS12NestedSafeContentsDecodeTemplate;
    } else {
        theTemplate = sec_PKCS12SafeContentsDecodeTemplate;
    }

    /* start the decoder context */
    safeContentsCtx->safeContentsA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena,
                                                              &safeContentsCtx->safeContents,
                                                              theTemplate);

    if (!safeContentsCtx->safeContentsA1Dcx) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* set the safeContents notify procedure to look for
     * and start the decode of safeBags.
     */

    SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->safeContentsA1Dcx,
                                 sec_pkcs12_decoder_safe_contents_notify,
                                 safeContentsCtx);

    return safeContentsCtx;

loser:
    /* in the case of an error, we want to finish the decoder
     * context and set the error flag.
     */

    if (safeContentsCtx && safeContentsCtx->safeContentsA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
        safeContentsCtx->safeContentsA1Dcx = NULL;
    }

    p12dcx->error = PR_TRUE;

    return NULL;
}

/* wrapper for updating safeContents.  this is set as the filter of
 * safeBag when there is a nested safeContents.
 */

static void
sec_pkcs12_decoder_nested_safe_contents_update(void *arg, const char *buf,
                                               unsigned long len, int depth,
                                               SEC_ASN1EncodingPart data_kind)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    /* check for an error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error || !safeContentsCtx->safeContentsA1Dcx) {
        return;
    }

    /* no need to update if no data sent in */
    if (!len || !buf) {
        return;
    }

    /* update the decoding context */
    p12dcx = safeContentsCtx->p12dcx;
    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsA1Dcx, buf, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    return;

loser:
    /* handle any errors.  If a decoding context is open, close it. */
    p12dcx->error = PR_TRUE;
    if (safeContentsCtx->safeContentsA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
        safeContentsCtx->safeContentsA1Dcx = NULL;
    }
}

/* whenever a new safeContentsSafeBag is encountered, we need
 * to init a safeContentsContext.
 */

static SECStatus
sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                  *safeContentsCtx)
{
    /* check for an error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return SECFailure;
    }

    safeContentsCtx->nestedSafeContentsCtx =
        sec_pkcs12_decoder_safe_contents_init_decode(safeContentsCtx->p12dcx,
                                                     PR_TRUE);
    if (!safeContentsCtx->nestedSafeContentsCtx) {
        return SECFailure;
    }

    /* set up new filter proc */
    SEC_ASN1DecoderSetNotifyProc(
        safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx,
        sec_pkcs12_decoder_safe_contents_notify,
        safeContentsCtx->nestedSafeContentsCtx);

    SEC_ASN1DecoderSetFilterProc(safeContentsCtx->currentSafeBagA1Dcx,
                                 sec_pkcs12_decoder_nested_safe_contents_update,
                                 safeContentsCtx->nestedSafeContentsCtx,
                                 PR_TRUE);

    return SECSuccess;
}

/* when the safeContents is done decoding, we need to reset the
 * proper filter and notify procs and close the decoding context
 */

static SECStatus
sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                   *safeContentsCtx)
{
    /* check for error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return SECFailure;
    }

    /* clean up */
    SEC_ASN1DecoderClearFilterProc(safeContentsCtx->currentSafeBagA1Dcx);
    SEC_ASN1DecoderClearNotifyProc(
        safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx);
    SEC_ASN1DecoderFinish(
        safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx);
    safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx = NULL;
    safeContentsCtx->nestedSafeContentsCtx = NULL;

    return SECSuccess;
}

/* wrapper for updating safeContents.  This is used when decoding
 * the nested safeContents and any authenticatedSafes.
 */

static void
sec_pkcs12_decoder_safe_contents_callback(void *arg, const char *buf,
                                          unsigned long len)
{
    SECStatus rv;
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;

    /* check for error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error || !safeContentsCtx->safeContentsA1Dcx) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* update the decoder */
    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsA1Dcx, buf, len);
    if (rv != SECSuccess) {
        /* if we fail while trying to decode a 'safe', it's probably because
         * we didn't have the correct password. */

        PORT_SetError(SEC_ERROR_BAD_PASSWORD);
        p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        SEC_PKCS7DecoderAbort(p12dcx->currentASafeP7Dcx, SEC_ERROR_BAD_PASSWORD);
        goto loser;
    }

    return;

loser:
    /* set the error and finish the context */
    p12dcx->error = PR_TRUE;
    if (safeContentsCtx->safeContentsA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
        safeContentsCtx->safeContentsA1Dcx = NULL;
    }

    return;
}

/* this is a wrapper for the ASN1 decoder to call SEC_PKCS7DecoderUpdate
 */

static void
sec_pkcs12_decoder_wrap_p7_update(void *arg, const char *data,
                                  unsigned long len, int depth,
                                  SEC_ASN1EncodingPart data_kind)
{
    SEC_PKCS7DecoderContext *p7dcx = (SEC_PKCS7DecoderContext *)arg;

    SEC_PKCS7DecoderUpdate(p7dcx, data, len);
}

/* notify function for decoding aSafes.  at the beginning,
 * of an authenticatedSafe, we start a decode of a safeContents.
 * at the end, we clean up the safeContents decoder context and
 * reset state variables
 */

static void
sec_pkcs12_decoder_asafes_notify(void *arg, PRBool before, void *dest,
                                 int real_depth)
{
    SEC_PKCS12DecoderContext *p12dcx;
    sec_PKCS12SafeContentsContext *safeContentsCtx;

    /* make sure no error occurred. */
    p12dcx = (SEC_PKCS12DecoderContext *)arg;
    if (!p12dcx || p12dcx->error) {
        return;
    }

    if (before) {

        /* init a new safeContentsContext */
        safeContentsCtx = sec_pkcs12_decoder_safe_contents_init_decode(p12dcx,
                                                                       PR_FALSE);
        if (!safeContentsCtx) {
            goto loser;
        }

        /* initiate the PKCS7ContentInfo decode */
        p12dcx->currentASafeP7Dcx = SEC_PKCS7DecoderStart(
            sec_pkcs12_decoder_safe_contents_callback,
            safeContentsCtx,
            p12dcx->pwfn, p12dcx->pwfnarg,
            sec_pkcs12_decoder_get_decrypt_key, p12dcx,
            sec_pkcs12_decoder_decryption_allowed);
        if (!p12dcx->currentASafeP7Dcx) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
        SEC_ASN1DecoderSetFilterProc(p12dcx->aSafeA1Dcx,
                                     sec_pkcs12_decoder_wrap_p7_update,
                                     p12dcx->currentASafeP7Dcx, PR_TRUE);
    }

    if (!before) {
        /* if one is being decoded, finish the decode */
        if (p12dcx->currentASafeP7Dcx != NULL) {
            SEC_PKCS7ContentInfo *cinfo;
            unsigned int cnt = p12dcx->safeContentsCnt - 1;
            safeContentsCtx = p12dcx->safeContentsList[cnt];
            if (safeContentsCtx->safeContentsA1Dcx) {
                SEC_ASN1DecoderClearFilterProc(p12dcx->aSafeA1Dcx);
                SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
                safeContentsCtx->safeContentsA1Dcx = NULL;
            }
            cinfo = SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx);
            p12dcx->currentASafeP7Dcx = NULL;
            if (!cinfo) {
                p12dcx->errorValue = PORT_GetError();
                goto loser;
            }
            SEC_PKCS7DestroyContentInfo(cinfo); /* don't leak it */
        }
    }

    return;

loser:
    /* set the error flag */
    p12dcx->error = PR_TRUE;
    return;
}

/* wrapper for updating asafes decoding context.  this function
 * writes data being decoded to disk, so that a mac can be computed
 * later.
 */

static void
sec_pkcs12_decoder_asafes_callback(void *arg, const char *buf,
                                   unsigned long len)
{
    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;
    SECStatus rv;

    if (!p12dcx || p12dcx->error) {
        return;
    }

    /* update the context */
    rv = SEC_ASN1DecoderUpdate(p12dcx->aSafeA1Dcx, buf, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        p12dcx->error = PR_TRUE;
        goto loser;
    }

    /* if we are writing to a file, write out the new information */
    if (p12dcx->dWrite) {
        unsigned long writeLen = (*p12dcx->dWrite)(p12dcx->dArg,
                                                   (unsigned char *)buf, len);
        if (writeLen != len) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
    }

    return;

loser:
    /* set the error flag */
    p12dcx->error = PR_TRUE;
    SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx);
    p12dcx->aSafeA1Dcx = NULL;

    return;
}

/* start the decode of an authenticatedSafe contentInfo.
 */

static SECStatus
sec_pkcs12_decode_start_asafes_cinfo(SEC_PKCS12DecoderContext *p12dcx)
{
    if (!p12dcx || p12dcx->error) {
        return SECFailure;
    }

    /* start the decode context */
    p12dcx->aSafeA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena,
                                              &p12dcx->authSafe,
                                              sec_PKCS12AuthenticatedSafeTemplate);
    if (!p12dcx->aSafeA1Dcx) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* set the notify function */
    SEC_ASN1DecoderSetNotifyProc(p12dcx->aSafeA1Dcx,
                                 sec_pkcs12_decoder_asafes_notify, p12dcx);

    /* begin the authSafe decoder context */
    p12dcx->aSafeP7Dcx = SEC_PKCS7DecoderStart(
        sec_pkcs12_decoder_asafes_callback, p12dcx,
        p12dcx->pwfn, p12dcx->pwfnarg, NULL, NULL, NULL);
    if (!p12dcx->aSafeP7Dcx) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* open the temp file for writing, if the digest functions were set */
    if (p12dcx->dOpen && (*p12dcx->dOpen)(p12dcx->dArg, PR_FALSE) != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }
    /* dOpen(dArg, PR_FALSE) creates the temp file */
    p12dcx->dIsOpen = PR_TRUE;

    return SECSuccess;

loser:
    p12dcx->error = PR_TRUE;

    if (p12dcx->aSafeA1Dcx) {
        SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx);
        p12dcx->aSafeA1Dcx = NULL;
    }

    if (p12dcx->aSafeP7Dcx) {
        SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
        p12dcx->aSafeP7Dcx = NULL;
    }

    return SECFailure;
}

/* wrapper for updating the safeContents.  this function is used as
 * a filter for the pfx when decoding the authenticated safes
 */

static void
sec_pkcs12_decode_asafes_cinfo_update(void *arg, const char *buf,
                                      unsigned long len, int depth,
                                      SEC_ASN1EncodingPart data_kind)
{
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    p12dcx = (SEC_PKCS12DecoderContext *)arg;
    if (!p12dcx || p12dcx->error) {
        return;
    }

    /* update the safeContents decoder */
    rv = SEC_PKCS7DecoderUpdate(p12dcx->aSafeP7Dcx, buf, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        goto loser;
    }

    return;

loser:

    /* did we find an error?  if so, close the context and set the
     * error flag.
     */

    SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
    p12dcx->aSafeP7Dcx = NULL;
    p12dcx->error = PR_TRUE;
}

/* notify procedure used while decoding the pfx.  When we encounter
 * the authSafes, we want to trigger the decoding of authSafes as well
 * as when we encounter the macData, trigger the decoding of it.  we do
 * this because we we are streaming the decoder and not decoding in place.
 * the pfx which is the destination, only has the version decoded into it.
 */

static void
sec_pkcs12_decoder_pfx_notify_proc(void *arg, PRBool before, void *dest,
                                   int real_depth)
{
    SECStatus rv;
    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;

    /* if an error occurs, clear the notifyProc and the filterProc
     * and continue.
     */

    if (p12dcx->error) {
        SEC_ASN1DecoderClearNotifyProc(p12dcx->pfxA1Dcx);
        SEC_ASN1DecoderClearFilterProc(p12dcx->pfxA1Dcx);
        return;
    }

    if (before && (dest == &p12dcx->pfx.encodedAuthSafe)) {

        /* we want to make sure this is a version we support */
        if (!sec_pkcs12_proper_version(&p12dcx->pfx)) {
            p12dcx->errorValue = SEC_ERROR_PKCS12_UNSUPPORTED_VERSION;
            goto loser;
        }

        /* start the decode of the aSafes cinfo... */
        rv = sec_pkcs12_decode_start_asafes_cinfo(p12dcx);
        if (rv != SECSuccess) {
            goto loser;
        }

        /* set the filter proc to update the authenticated safes. */
        SEC_ASN1DecoderSetFilterProc(p12dcx->pfxA1Dcx,
                                     sec_pkcs12_decode_asafes_cinfo_update,
                                     p12dcx, PR_TRUE);
    }

    if (!before && (dest == &p12dcx->pfx.encodedAuthSafe)) {

        /* we are done decoding the authenticatedSafes, so we need to
         * finish the decoderContext and clear the filter proc
         * and close the hmac callback, if present
         */

        p12dcx->aSafeCinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
        p12dcx->aSafeP7Dcx = NULL;
        if (!p12dcx->aSafeCinfo) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
        SEC_ASN1DecoderClearFilterProc(p12dcx->pfxA1Dcx);
        if (p12dcx->dClose && ((*p12dcx->dClose)(p12dcx->dArg, PR_FALSE) != SECSuccess)) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
    }

    return;

loser:
    p12dcx->error = PR_TRUE;
}

/*  default implementations of the open/close/read/write functions for
    SEC_PKCS12DecoderStart
*/


#define DEFAULT_TEMP_SIZE 4096

static SECStatus
p12u_DigestOpen(void *arg, PRBool readData)
{
    SEC_PKCS12DecoderContext *p12cxt = arg;

    p12cxt->currentpos = 0;

    if (PR_FALSE == readData) {
        /* allocate an initial buffer */
        p12cxt->filesize = 0;
        p12cxt->allocated = DEFAULT_TEMP_SIZE;
        p12cxt->buffer = PORT_Alloc(DEFAULT_TEMP_SIZE);
        PR_ASSERT(p12cxt->buffer);
    } else {
        PR_ASSERT(p12cxt->buffer);
        if (!p12cxt->buffer) {
            return SECFailure; /* no data to read */
        }
    }

    return SECSuccess;
}

static SECStatus
p12u_DigestClose(void *arg, PRBool removeFile)
{
    SEC_PKCS12DecoderContext *p12cxt = arg;

    PR_ASSERT(p12cxt);
    if (!p12cxt) {
        return SECFailure;
    }
    p12cxt->currentpos = 0;

    if (PR_TRUE == removeFile) {
        PR_ASSERT(p12cxt->buffer);
        if (!p12cxt->buffer) {
            return SECFailure;
        }
        if (p12cxt->buffer) {
            PORT_Free(p12cxt->buffer);
            p12cxt->buffer = NULL;
            p12cxt->allocated = 0;
            p12cxt->filesize = 0;
        }
    }

    return SECSuccess;
}

static int
p12u_DigestRead(void *arg, unsigned char *buf, unsigned long len)
{
    int toread = len;
    SEC_PKCS12DecoderContext *p12cxt = arg;

    if (!buf || len == 0 || !p12cxt->buffer) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return -1;
    }

    if ((p12cxt->filesize - p12cxt->currentpos) < (long)len) {
        /* trying to read past the end of the buffer */
        toread = p12cxt->filesize - p12cxt->currentpos;
    }
    memcpy(buf, (char *)p12cxt->buffer + p12cxt->currentpos, toread);
    p12cxt->currentpos += toread;
    return toread;
}

static int
p12u_DigestWrite(void *arg, unsigned char *buf, unsigned long len)
{
    SEC_PKCS12DecoderContext *p12cxt = arg;

    if (!buf || len == 0) {
        return -1;
    }

    if (p12cxt->currentpos + (long)len > p12cxt->filesize) {
        p12cxt->filesize = p12cxt->currentpos + len;
    } else {
        p12cxt->filesize += len;
    }
    if (p12cxt->filesize > p12cxt->allocated) {
        void *newbuffer;
        size_t newsize = p12cxt->filesize + DEFAULT_TEMP_SIZE;
        newbuffer = PORT_Realloc(p12cxt->buffer, newsize);
        if (NULL == newbuffer) {
            return -1; /* can't extend the buffer */
        }
        p12cxt->buffer = newbuffer;
        p12cxt->allocated = newsize;
    }
    PR_ASSERT(p12cxt->buffer);
    memcpy((char *)p12cxt->buffer + p12cxt->currentpos, buf, len);
    p12cxt->currentpos += len;
    return len;
}

/* SEC_PKCS12DecoderStart
 *      Creates a decoder context for decoding a PKCS 12 PDU objct.
 *      This function sets up the initial decoding context for the
 *      PFX and sets the needed state variables.
 *
 *      pwitem - the password for the hMac and any encoded safes.
 *               this should be changed to take a callback which retrieves
 *               the password.  it may be possible for different safes to
 *               have different passwords.  also, the password is already
 *               in unicode.  it should probably be converted down below via
 *               a unicode conversion callback.
 *      slot - the slot to import the dataa into should multiple slots
 *               be supported based on key type and cert type?
 *      dOpen, dClose, dRead, dWrite - digest routines for writing data
 *               to a file so it could be read back and the hmac recomputed
 *               and verified.  doesn't seem to be a way for both encoding
 *               and decoding to be single pass, thus the need for these
 *               routines.
 *      dArg - the argument for dOpen, etc.
 *
 *      if NULL == dOpen == dClose == dRead == dWrite == dArg, then default
 *      implementations using a memory buffer are used
 *
 *      This function returns the decoder context, if it was successful.
 *      Otherwise, null is returned.
 */

SEC_PKCS12DecoderContext *
SEC_PKCS12DecoderStart(SECItem *pwitem, PK11SlotInfo *slot, void *wincx,
                       digestOpenFn dOpen, digestCloseFn dClose,
                       digestIOFn dRead, digestIOFn dWrite, void *dArg)
{
    SEC_PKCS12DecoderContext *p12dcx;
    PLArenaPool *arena;
    PRInt32 forceUnicode = PR_FALSE;
    SECStatus rv;

    arena = PORT_NewArena(2048); /* different size? */
    if (!arena) {
        return NULL; /* error is already set */
    }

    /* allocate the decoder context and set the state variables */
    p12dcx = PORT_ArenaZNew(arena, SEC_PKCS12DecoderContext);
    if (!p12dcx) {
        goto loser; /* error is already set */
    }

    if (!dOpen && !dClose && !dRead && !dWrite && !dArg) {
        /* use default implementations */
        dOpen = p12u_DigestOpen;
        dClose = p12u_DigestClose;
        dRead = p12u_DigestRead;
        dWrite = p12u_DigestWrite;
        dArg = (void *)p12dcx;
    }

    p12dcx->arena = arena;
    p12dcx->pwitem = pwitem;
    p12dcx->slot = (slot ? PK11_ReferenceSlot(slot)
                         : PK11_GetInternalKeySlot());
    p12dcx->wincx = wincx;
    p12dcx->tokenCAs = SECPKCS12TargetTokenNoCAs;
#ifdef IS_LITTLE_ENDIAN
    p12dcx->swapUnicodeBytes = PR_TRUE;
#else
    p12dcx->swapUnicodeBytes = PR_FALSE;
#endif
    rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode);
    if (rv != SECSuccess) {
        goto loser;
    }
    p12dcx->forceUnicode = forceUnicode;
    p12dcx->errorValue = 0;
    p12dcx->error = PR_FALSE;

    /* start the decoding of the PFX and set the notify proc
     * for the PFX item.
     */

    p12dcx->pfxA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->pfx,
                                            sec_PKCS12PFXItemTemplate);
    if (!p12dcx->pfxA1Dcx) {
        PK11_FreeSlot(p12dcx->slot);
        goto loser;
    }

    SEC_ASN1DecoderSetNotifyProc(p12dcx->pfxA1Dcx,
                                 sec_pkcs12_decoder_pfx_notify_proc,
                                 p12dcx);

    /* set up digest functions */
    p12dcx->dOpen = dOpen;
    p12dcx->dWrite = dWrite;
    p12dcx->dClose = dClose;
    p12dcx->dRead = dRead;
    p12dcx->dArg = dArg;
    p12dcx->dIsOpen = PR_FALSE;

    p12dcx->keyList = NULL;
    p12dcx->decitem.type = 0;
    p12dcx->decitem.der = NULL;
    p12dcx->decitem.hasKey = PR_FALSE;
    p12dcx->decitem.friendlyName = NULL;
    p12dcx->iteration = 0;

    return p12dcx;

loser:
    PORT_FreeArena(arena, PR_TRUE);
    return NULL;
}

SECStatus
SEC_PKCS12DecoderSetTargetTokenCAs(SEC_PKCS12DecoderContext *p12dcx,
                                   SECPKCS12TargetTokenCAs tokenCAs)
{
    if (!p12dcx || p12dcx->error) {
        return SECFailure;
    }
    p12dcx->tokenCAs = tokenCAs;
    return SECSuccess;
}

/* SEC_PKCS12DecoderUpdate
 *      Streaming update sending more data to the decoder.  If
 *      an error occurs, SECFailure is returned.
 *
 *      p12dcx - the decoder context
 *      data, len - the data buffer and length of data to send to
 *              the update functions.
 */

SECStatus
SEC_PKCS12DecoderUpdate(SEC_PKCS12DecoderContext *p12dcx,
                        unsigned char *data, unsigned long len)
{
    SECStatus rv;

    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* update the PFX decoder context */
    rv = SEC_ASN1DecoderUpdate(p12dcx->pfxA1Dcx, (const char *)data, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        goto loser;
    }

    return SECSuccess;

loser:

    p12dcx->error = PR_TRUE;
    return SECFailure;
}

/* This should be a nice sized buffer for reading in data (potentially large
** amounts) to be MACed.  It should be MUCH larger than HASH_LENGTH_MAX.
*/

#define IN_BUF_LEN 1024
#ifdef DEBUG
static const char bufferEnd[] = { "BufferEnd" };
#endif
#define FUDGE 128 /* must be as large as bufferEnd or more. */

/* verify the hmac by reading the data from the temporary file
 * using the routines specified when the decodingContext was
 * created and return SECSuccess if the hmac matches.
 */

static SECStatus
sec_pkcs12_decoder_verify_mac(SEC_PKCS12DecoderContext *p12dcx)
{
    PK11Context *pk11cx = NULL;
    PK11SymKey *symKey = NULL;
    unsigned char *buf;
    SECStatus rv = SECFailure;
    SECStatus lrv;
    unsigned int bufLen;
    int bytesRead;
    SECItem hmacRes;
    SECItem ignore = { 0 };
    CK_MECHANISM_TYPE hmacMech;

    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    buf = (unsigned char *)PORT_Alloc(IN_BUF_LEN + FUDGE);
    if (!buf)
        return SECFailure; /* error code has been set. */

#ifdef DEBUG
    memcpy(buf + IN_BUF_LEN, bufferEnd, sizeof bufferEnd);
#endif

    /* generate hmac key */
    symKey = sec_pkcs12_integrity_key(p12dcx->slot, &p12dcx->macData,
                                      p12dcx->pwitem, &hmacMech, PR_TRUE,
                                      p12dcx->wincx);
    if (symKey == NULL) {
        goto loser;
    }

    /* init hmac */
    pk11cx = PK11_CreateContextBySymKey(hmacMech, CKA_SIGN, symKey, &ignore);
    if (!pk11cx) {
        goto loser;
    }
    lrv = PK11_DigestBegin(pk11cx);
    if (lrv == SECFailure) {
        goto loser;
    }

    /* try to open the data for readback */
    if (p12dcx->dOpen && ((*p12dcx->dOpen)(p12dcx->dArg, PR_TRUE) != SECSuccess)) {
        goto loser;
    }

    /* read the data back IN_BUF_LEN bytes at a time and recompute
     * the hmac.  if fewer bytes are read than are requested, it is
     * assumed that the end of file has been reached. if bytesRead
     * is returned as -1, then an error occurred reading from the
     * file.
     */

    do {
        bytesRead = (*p12dcx->dRead)(p12dcx->dArg, buf, IN_BUF_LEN);
        if (bytesRead < 0) {
            PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_READ);
            goto loser;
        }
        PORT_Assert(bytesRead <= IN_BUF_LEN);
        PORT_Assert(!memcmp(buf + IN_BUF_LEN, bufferEnd, sizeof bufferEnd));

        if (bytesRead > IN_BUF_LEN) {
            /* dRead callback overflowed buffer. */
            PORT_SetError(SEC_ERROR_INPUT_LEN);
            goto loser;
        }

        if (bytesRead) {
            lrv = PK11_DigestOp(pk11cx, buf, bytesRead);
            if (lrv == SECFailure) {
                goto loser;
            }
        }
    } while (bytesRead == IN_BUF_LEN);

    /* finish the hmac context */
    lrv = PK11_DigestFinal(pk11cx, buf, &bufLen, IN_BUF_LEN);
    if (lrv == SECFailure) {
        goto loser;
    }

    hmacRes.data = buf;
    hmacRes.len = bufLen;

    /* is the hmac computed the same as the hmac which was decoded? */
    rv = SECSuccess;
    if (SECITEM_CompareItem(&hmacRes, &p12dcx->macData.safeMac.digest) != SECEqual) {
        PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC);
        rv = SECFailure;
    }

loser:
    /* close the file and remove it */
    if (p12dcx->dClose) {
        (*p12dcx->dClose)(p12dcx->dArg, PR_TRUE);
        p12dcx->dIsOpen = PR_FALSE;
    }

    if (pk11cx) {
        PK11_DestroyContext(pk11cx, PR_TRUE);
    }
    if (symKey) {
        PK11_FreeSymKey(symKey);
    }
    PORT_ZFree(buf, IN_BUF_LEN + FUDGE);

    return rv;
}

/* SEC_PKCS12DecoderVerify
 *      Verify the macData or the signature of the decoded PKCS 12 PDU.
 *      If the signature or the macData do not match, SECFailure is
 *      returned.
 *
 *      p12dcx - the decoder context
 */

SECStatus
SEC_PKCS12DecoderVerify(SEC_PKCS12DecoderContext *p12dcx)
{
    SECStatus rv = SECSuccess;

    /* make sure that no errors have occurred... */
    if (!p12dcx) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (p12dcx->error) {
        /* error code is already set! PORT_SetError(p12dcx->errorValue); */
        return SECFailure;
    }

    rv = SEC_ASN1DecoderFinish(p12dcx->pfxA1Dcx);
    p12dcx->pfxA1Dcx = NULL;
    if (rv != SECSuccess) {
        return rv;
    }

    /* check the signature or the mac depending on the type of
     * integrity used.
     */

    if (p12dcx->pfx.encodedMacData.len) {
        rv = SEC_ASN1DecodeItem(p12dcx->arena, &p12dcx->macData,
                                sec_PKCS12MacDataTemplate,
                                &p12dcx->pfx.encodedMacData);
        if (rv == SECSuccess) {
            return sec_pkcs12_decoder_verify_mac(p12dcx);
        }
        return rv;
    }
    if (SEC_PKCS7VerifySignature(p12dcx->aSafeCinfo, certUsageEmailSigner,
                                 PR_FALSE)) {
        return SECSuccess;
    }
    PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC);
    return SECFailure;
}

/* SEC_PKCS12DecoderFinish
 *      Free any open ASN1 or PKCS7 decoder contexts and then
 *      free the arena pool which everything should be allocated
 *      from.  This function should be called upon completion of
 *      decoding and installing of a pfx pdu.  This should be
 *      called even if an error occurs.
 *
 *      p12dcx - the decoder context
 */

void
SEC_PKCS12DecoderFinish(SEC_PKCS12DecoderContext *p12dcx)
{
    unsigned int i;

    if (!p12dcx) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    if (p12dcx->pfxA1Dcx) {
        SEC_ASN1DecoderFinish(p12dcx->pfxA1Dcx);
        p12dcx->pfxA1Dcx = NULL;
    }

    if (p12dcx->aSafeA1Dcx) {
        SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx);
        p12dcx->aSafeA1Dcx = NULL;
    }

    /* cleanup any old ASN1 decoder contexts */
    for (i = 0; i < p12dcx->safeContentsCnt; ++i) {
        sec_PKCS12SafeContentsContext *safeContentsCtx, *nested;
        safeContentsCtx = p12dcx->safeContentsList[i];
        if (safeContentsCtx) {
            nested = safeContentsCtx->nestedSafeContentsCtx;
            while (nested) {
                if (nested->safeContentsA1Dcx) {
                    SEC_ASN1DecoderFinish(nested->safeContentsA1Dcx);
                    nested->safeContentsA1Dcx = NULL;
                }
                nested = nested->nestedSafeContentsCtx;
            }
            if (safeContentsCtx->safeContentsA1Dcx) {
                SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
                safeContentsCtx->safeContentsA1Dcx = NULL;
            }
        }
    }

    if (p12dcx->currentASafeP7Dcx &&
        p12dcx->currentASafeP7Dcx != p12dcx->aSafeP7Dcx) {
        SEC_PKCS7ContentInfo *cinfo;
        cinfo = SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx);
        if (cinfo) {
            SEC_PKCS7DestroyContentInfo(cinfo); /* don't leak it */
        }
    }
    p12dcx->currentASafeP7Dcx = NULL;

    if (p12dcx->aSafeP7Dcx) {
        SEC_PKCS7ContentInfo *cinfo;
        cinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
        if (cinfo) {
            SEC_PKCS7DestroyContentInfo(cinfo);
        }
        p12dcx->aSafeP7Dcx = NULL;
    }

    if (p12dcx->aSafeCinfo) {
        SEC_PKCS7DestroyContentInfo(p12dcx->aSafeCinfo);
        p12dcx->aSafeCinfo = NULL;
    }

    if (p12dcx->decitem.type != 0 && p12dcx->decitem.der != NULL) {
        SECITEM_FreeItem(p12dcx->decitem.der, PR_TRUE);
    }
    if (p12dcx->decitem.friendlyName != NULL) {
        SECITEM_FreeItem(p12dcx->decitem.friendlyName, PR_TRUE);
    }

    if (p12dcx->slot) {
        PK11_FreeSlot(p12dcx->slot);
        p12dcx->slot = NULL;
    }

    if (p12dcx->dIsOpen && p12dcx->dClose) {
        (*p12dcx->dClose)(p12dcx->dArg, PR_TRUE);
        p12dcx->dIsOpen = PR_FALSE;
    }

    if (p12dcx->arena) {
        PORT_FreeArena(p12dcx->arena, PR_TRUE);
    }
}

static SECStatus
sec_pkcs12_decoder_set_attribute_value(sec_PKCS12SafeBag *bag,
                                       SECOidTag attributeType,
                                       SECItem *attrValue)
{
    int i = 0;
    SECOidData *oid;

    if (!bag || !attrValue) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    oid = SECOID_FindOIDByTag(attributeType);
    if (!oid) {
        return SECFailure;
    }

    if (!bag->attribs) {
        bag->attribs =
            PORT_ArenaZNewArray(bag->arena, sec_PKCS12Attribute *, 2);
    } else {
        while (bag->attribs[i])
            i++;
        bag->attribs = PORT_ArenaGrowArray(bag->arena, bag->attribs,
                                           sec_PKCS12Attribute *, i + 1, i + 2);
    }

    if (!bag->attribs) {
        return SECFailure;
    }

    bag->attribs[i] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute);
    if (!bag->attribs[i]) {
        return SECFailure;
    }

    bag->attribs[i]->attrValue = PORT_ArenaZNewArray(bag->arena, SECItem *, 2);
    if (!bag->attribs[i]->attrValue) {
        return SECFailure;
    }

    bag->attribs[i + 1] = NULL;
    bag->attribs[i]->attrValue[0] = attrValue;
    bag->attribs[i]->attrValue[1] = NULL;

    return SECITEM_CopyItem(bag->arena, &bag->attribs[i]->attrType, &oid->oid);
}

static SECItem *
sec_pkcs12_get_attribute_value(sec_PKCS12SafeBag *bag,
                               SECOidTag attributeType)
{
    int i;

    if (!bag->attribs) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    for (i = 0; bag->attribs[i] != NULL; i++) {
        if (SECOID_FindOIDTag(&bag->attribs[i]->attrType) == attributeType) {
            return bag->attribs[i]->attrValue[0];
        }
    }
    return NULL;
}

/* For now, this function will merely remove any ":"
 * in the nickname which the PK11 functions may have
 * placed there.  This will keep dual certs from appearing
 * twice under "Your" certificates when imported onto smart
 * cards.  Once with the name "Slot:Cert" and another with
 * the nickname "Slot:Slot:Cert"
 */

static void
sec_pkcs12_sanitize_nickname(PK11SlotInfo *slot, SECItem *nick)
{
    char *nickname;
    char *delimit;
    int delimitlen;

    nickname = (char *)nick->data;
    if ((delimit = PORT_Strchr(nickname, ':')) != NULL) {
        char *slotName;
        int slotNameLen;

        slotNameLen = delimit - nickname;
        slotName = PORT_NewArray(char, (slotNameLen + 1));
        PORT_Assert(slotName);
        if (slotName == NULL) {
            /* What else can we do?*/
            return;
        }
        PORT_Memcpy(slotName, nickname, slotNameLen);
        slotName[slotNameLen] = '\0';
        if (PORT_Strcmp(PK11_GetTokenName(slot), slotName) == 0) {
            delimitlen = PORT_Strlen(delimit + 1);
            PORT_Memmove(nickname, delimit + 1, delimitlen + 1);
            nick->len = delimitlen;
        }
        PORT_Free(slotName);
    }
}

static SECItem *
sec_pkcs12_get_nickname(sec_PKCS12SafeBag *bag)
{
    SECItem *src, *dest;

    if (!bag) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    src = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME);

    /* The return value src is 16-bit Unicode characters, in big-endian format.
     * Check if it is NULL or empty name.
     */

    if (!src || !src->data || src->len < 2 || (!src->data[0] && !src->data[1])) {
        return NULL;
    }

    dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if (!dest) {
        goto loser;
    }
    if (!sec_pkcs12_convert_item_to_unicode(NULL, dest, src, PR_FALSE,
                                            PR_FALSE, PR_FALSE)) {
        goto loser;
    }

    sec_pkcs12_sanitize_nickname(bag->slot, dest);

    return dest;

loser:
    if (dest) {
        SECITEM_ZfreeItem(dest, PR_TRUE);
    }

    bag->problem = PR_TRUE;
    bag->error = PORT_GetError();
    return NULL;
}

static SECStatus
sec_pkcs12_set_nickname(sec_PKCS12SafeBag *bag, SECItem *name)
{
    sec_PKCS12Attribute *attr = NULL;
    SECOidData *oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_FRIENDLY_NAME);

    if (!bag || !bag->arena || !name) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!bag->attribs) {
        if (!oid) {
            goto loser;
        }

        bag->attribs =
            PORT_ArenaZNewArray(bag->arena, sec_PKCS12Attribute *, 2);
        if (!bag->attribs) {
            goto loser;
        }
        bag->attribs[0] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute);
        if (!bag->attribs[0]) {
            goto loser;
        }
        bag->attribs[1] = NULL;

        attr = bag->attribs[0];
        if (SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) != SECSuccess) {
            goto loser;
        }
    } else {
        int i;
        for (i = 0; bag->attribs[i]; i++) {
            if (SECOID_FindOIDTag(&bag->attribs[i]->attrType) == SEC_OID_PKCS9_FRIENDLY_NAME) {
                attr = bag->attribs[i];
                break;
            }
        }
        if (!attr) {
            if (!oid) {
                goto loser;
            }
            bag->attribs = PORT_ArenaGrowArray(bag->arena, bag->attribs,
                                               sec_PKCS12Attribute *, i + 1, i + 2);
            if (!bag->attribs) {
                goto loser;
            }
            bag->attribs[i] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute);
            if (!bag->attribs[i]) {
                goto loser;
            }
            bag->attribs[i + 1] = NULL;
            attr = bag->attribs[i];
            if (SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) != SECSuccess) {
                goto loser;
            }
        }
    }

    PORT_Assert(attr);
    if (!attr->attrValue) {
        attr->attrValue = PORT_ArenaZNewArray(bag->arena, SECItem *, 2);
        if (!attr->attrValue) {
            goto loser;
        }
        attr->attrValue[0] = PORT_ArenaZNew(bag->arena, SECItem);
        if (!attr->attrValue[0]) {
            goto loser;
        }
        attr->attrValue[1] = NULL;
    }

    name->len = PORT_Strlen((char *)name->data);
    if (!sec_pkcs12_convert_item_to_unicode(bag->arena, attr->attrValue[0],
                                            name, PR_FALSE, PR_FALSE, PR_TRUE)) {
        goto loser;
    }

    return SECSuccess;

loser:
    bag->problem = PR_TRUE;
    bag->error = PORT_GetError();
    return SECFailure;
}

static SECStatus
sec_pkcs12_get_key_info(sec_PKCS12SafeBag *key)
{
    int i = 0;
    SECKEYPrivateKeyInfo *pki = NULL;

    if (!key) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* if the bag does *not* contain an unencrypted PrivateKeyInfo
     * then we cannot convert the attributes.  We are propagating
     * attributes within the PrivateKeyInfo to the SafeBag level.
     */

    if (SECOID_FindOIDTag(&(key->safeBagType)) !=
        SEC_OID_PKCS12_V1_KEY_BAG_ID) {
        return SECSuccess;
    }

    pki = key->safeBagContent.pkcs8KeyBag;

    if (!pki || !pki->attributes) {
        return SECSuccess;
    }

    while (pki->attributes[i]) {
        SECOidTag tag = SECOID_FindOIDTag(&pki->attributes[i]->attrType);

        if (tag == SEC_OID_PKCS9_LOCAL_KEY_ID ||
            tag == SEC_OID_PKCS9_FRIENDLY_NAME) {
            SECItem *attrValue = sec_pkcs12_get_attribute_value(key, tag);
            if (!attrValue) {
                if (sec_pkcs12_decoder_set_attribute_value(key, tag,
                                                           pki->attributes[i]->attrValue[0]) != SECSuccess) {
                    key->problem = PR_TRUE;
                    key->error = PORT_GetError();
                    return SECFailure;
                }
            }
        }
        i++;
    }

    return SECSuccess;
}

/* retrieve the nickname for the certificate bag.  first look
 * in the cert bag, otherwise get it from the key.
 */

static SECItem *
sec_pkcs12_get_nickname_for_cert(sec_PKCS12SafeBag *cert,
                                 sec_PKCS12SafeBag *key)
{
    SECItem *nickname;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    nickname = sec_pkcs12_get_nickname(cert);
    if (nickname) {
        return nickname;
    }

    if (key) {
        nickname = sec_pkcs12_get_nickname(key);

        if (nickname && sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) {
            SECITEM_ZfreeItem(nickname, PR_TRUE);
            return NULL;
        }
    }

    return nickname;
}

/* set the nickname for the certificate */
static SECStatus
sec_pkcs12_set_nickname_for_cert(sec_PKCS12SafeBag *cert,
                                 sec_PKCS12SafeBag *key,
                                 SECItem *nickname)
{
    if (!nickname || !cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) {
        return SECFailure;
    }

    if (key) {
        if (sec_pkcs12_set_nickname(key, nickname) != SECSuccess) {
            cert->problem = PR_TRUE;
            cert->error = key->error;
            return SECFailure;
        }
    }

    return SECSuccess;
}

/* retrieve the DER cert from the cert bag */
static SECItem *
sec_pkcs12_get_der_cert(sec_PKCS12SafeBag *cert)
{
    if (!cert || !cert->safeBagContent.certBag) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    if (SECOID_FindOIDTag(&cert->safeBagType) != SEC_OID_PKCS12_V1_CERT_BAG_ID) {
        return NULL;
    }

    /* only support X509 certs not SDSI */
    if (SECOID_FindOIDTag(&cert->safeBagContent.certBag->bagID) != SEC_OID_PKCS9_X509_CERT) {
        return NULL;
    }

    return SECITEM_DupItem(&(cert->safeBagContent.certBag->value.x509Cert));
}

struct certNickInfo {
    PLArenaPool *arena;
    unsigned int nNicks;
    SECItem **nickList;
    unsigned int error;
};

/* callback for traversing certificates to gather the nicknames
 * used in a particular traversal.  for instance, when using
 * CERT_TraversePermCertsForSubject, gather the nicknames and
 * store them in the certNickInfo for a particular DN.
 *
 * this handles the case where multiple nicknames are allowed
 * for the same dn, which is not currently allowed, but may be
 * in the future.
 */

static SECStatus
gatherNicknames(CERTCertificate *cert, void *arg)
{
    struct certNickInfo *nickArg = (struct certNickInfo *)arg;
    SECItem tempNick;
    unsigned int i;

    if (!cert || !nickArg || nickArg->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!cert->nickname) {
        return SECSuccess;
    }

    tempNick.data = (unsigned char *)cert->nickname;
    tempNick.len = PORT_Strlen(cert->nickname) + 1;
    tempNick.type = siAsciiString;

    /* do we already have the nickname in the list? */
    if (nickArg->nNicks > 0) {

        /* nicknames have been encountered, but there is no list -- bad */
        if (!nickArg->nickList) {
            nickArg->error = SEC_ERROR_INVALID_ARGS;
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return SECFailure;
        }

        for (i = 0; i < nickArg->nNicks; i++) {
            if (SECITEM_CompareItem(nickArg->nickList[i], &tempNick) == SECEqual) {
                return SECSuccess;
            }
        }
    }

    /* add the nickname to the list */
    nickArg->nickList = (nickArg->nNicks == 0)
                            ? PORT_ArenaZNewArray(nickArg->arena, SECItem *, 2)
                            : PORT_ArenaGrowArray(nickArg->arena, nickArg->nickList, SECItem *,
                                                  nickArg->nNicks + 1, nickArg->nNicks + 2);

    if (!nickArg->nickList) {
        nickArg->error = SEC_ERROR_NO_MEMORY;
        return SECFailure;
    }

    nickArg->nickList[nickArg->nNicks] =
        PORT_ArenaZNew(nickArg->arena, SECItem);
    if (!nickArg->nickList[nickArg->nNicks]) {
        nickArg->error = PORT_GetError();
        return SECFailure;
    }

    if (SECITEM_CopyItem(nickArg->arena, nickArg->nickList[nickArg->nNicks],
                         &tempNick) != SECSuccess) {
        nickArg->error = PORT_GetError();
        return SECFailure;
    }

    nickArg->nNicks++;

    return SECSuccess;
}

/* traverses the certs in the data base or in the token for the
 * DN to see if any certs currently have a nickname set.
 * If so, return it.
 */

static SECItem *
sec_pkcs12_get_existing_nick_for_dn(sec_PKCS12SafeBag *cert)
{
    struct certNickInfo *nickArg = NULL;
    SECItem *derCert, *returnDn = NULL;
    PLArenaPool *arena = NULL;
    CERTCertificate *tempCert;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    derCert = sec_pkcs12_get_der_cert(cert);
    if (!derCert) {
        return NULL;
    }

    tempCert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
    if (!tempCert) {
        returnDn = NULL;
        goto loser;
    }

    arena = PORT_NewArena(1024);
    if (!arena) {
        returnDn = NULL;
        goto loser;
    }
    nickArg = PORT_ArenaZNew(arena, struct certNickInfo);
    if (!nickArg) {
        returnDn = NULL;
        goto loser;
    }
    nickArg->error = 0;
    nickArg->nNicks = 0;
    nickArg->nickList = NULL;
    nickArg->arena = arena;

    /* if the token is local, first traverse the cert database
     * then traverse the token.
     */

    if (PK11_TraverseCertsForSubjectInSlot(tempCert, cert->slot, gatherNicknames,
                                           (void *)nickArg) != SECSuccess) {
        returnDn = NULL;
        goto loser;
    }

    if (nickArg->error) {
        /* XXX do we want to set the error? */
        returnDn = NULL;
        goto loser;
    }

    if (nickArg->nNicks == 0) {
        returnDn = NULL;
        goto loser;
    }

    /* set it to the first name, for now.  handle multiple names? */
    returnDn = SECITEM_DupItem(nickArg->nickList[0]);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }

    if (tempCert) {
        CERT_DestroyCertificate(tempCert);
    }

    if (derCert) {
        SECITEM_FreeItem(derCert, PR_TRUE);
    }

    return (returnDn);
}

/* counts certificates found for a given traversal function */
static SECStatus
countCertificate(CERTCertificate *cert, void *arg)
{
    unsigned int *nCerts = (unsigned int *)arg;

    if (!cert || !arg) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    (*nCerts)++;
    return SECSuccess;
}

static PRBool
sec_pkcs12_certs_for_nickname_exist(SECItem *nickname, PK11SlotInfo *slot)
{
    unsigned int nCerts = 0;

    if (!nickname || !slot) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return PR_TRUE;
    }

    /* we want to check the local database first if we are importing to it */
--> --------------------

--> maximum size reached

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

Messung V0.5
C=93 H=79 G=86

¤ Dauer der Verarbeitung: 0.10 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.