You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



You are here: NSS project page > Content Version Numbers in cert7.db

Content Version Numbers in the Certificate Database

Newsgroup: mozilla.dev.tech.crypto
Technical contact: Frederick Roeber
Yell at the manager: Bob Lord

NSS has traditionally contained a set of pre-trusted root certificates provided by a few root certificate authorities. These authorities and the basic trust information can be seen in the Communicator Security Advisor (click on the lock), under "Certificates / Signers."

Some companies consider their certificates "proprietary software," so we had to sanitize out their roots and replace them with a couple sample certificates to illustrate how the code works. See the file lib/cert/certinit.c.

In the original codebase, all certificate and trust information is kept in the certificate database (e.g., "cert7.db"; the physical format of the certificate database is described and available). Upon NSS initialization, this database would be created or updated with the built-in information. (This information proved much more dynamic than originally expected; in the future, we wish to move this basic trust info to one or more specific "data-only" PKCS#11 modules: see the code in lib/ckfw/builtins/).

The code that creates or updates the database is in lib/cert/certinit.c. Specifically, "CERT_InitCertDB" creates a new database, and "CERT_AddNewCerts" updates the database when a new version of the client is run with an old database.

The certificates are stored in base64 form in static char arrays. The array "initialcerts" provides instructions on updating a database. This is an array of structures with the following format:

typedef struct {
    char *cert;
    char *nickname;
    CERTCertTrust trust;
    int updateVersion;
    certUpdateOp op;
    CERTCertTrust trustDelta;
} certInitEntry;

The cert field points to the base64-encoded cert. The nickname contains the text name that appears in the security advisor. The trust field contains the basic trust bits for the three subjects of SSL, S/MIME, and object signing. Usually three flags have to be set: CERTDB_VALID_CA, CERTDB_TRUSTED_CA, and CERTDB_NS_TRUSTED_CA. (These flags are defined in lib/cert/certdb.h). The update version is the content version number at which this entry appeared. The code's version is CERT_DB_CONTENT_VERSION in certdb.h. The op is the update operation to perform, usually certUpdateAdd or certUpdateAddTrust. Finally, the trustDelta field is used for some update operations.

When the database is being updated, CERT_AddNewCerts loops through the initialcerts array. Any entry with an updateVersion greater than the ContentVersion field in the database is acted upon. The certUpdateOp operations are:

  • certUpdateNone
    This is a no-op.
  • certUpdateAdd
    The cert is added to the database, with the trust information as specified in "trust."
  • certUpdateDelete
    The cert is deleted from the database. I don't think we've ever used this.
  • certUpdateAddTrust
    If the cert exists, the trust bits in "trustDelta" are OR'ed into the trust info stored in the database. In addition to allowing the root CA vendors to change their mind, this allowed us to add the "step-up" bit to roots when the vendors received export clearance to issue "step-up" certs. ("Step-up" certs are ones which let export-grade clients use strong crypto.)
  • certUpdateRemoveTrust
    If the cert exists, the trust bits in "trustDelta" are removed from the trust info stored in the database. This allows root CA vendors to change their mind.
  • certUpdateSetTrust
    If the cert exists, the trust info stored in the database is set to be the value in "trustDelta." This also allows root CA vendors to change their mind.

Example

As an example, let us suppose that the first version of the NSS code contained two root CA certificates from Example.com: one for server certs, and one for individual certs. Originally, they wanted the first to be trusted for SSL server authentication, and the second for s/mime and object signing. The first version of the code would look like this:

In certdb.h: #define CERT_DB_CONTENT_VERSION 1

In certinit.c:

static char example_com_server_ca[] = "....";
static char example_com_individual_ca[] = "....";
static certInitEntry initialcerts[] = {
    {
        example_com_server_ca,
        "Example.com Server CA",
        { DEFAULT_TRUST_FLAGS, 0, 0 },
        1,
        certUpdateAdd,
        { 0, 0, 0 }
    },
    {
        example_com_individual_ca,
        "Example.com Individual CA",
        { 0, DEFAULT_TRUST_FLAGS, DEFAULT_TRUST_FLAGS },
        1,
        certUpdateAdd,
        { 0, 0, 0 }
    }
};

Then let us suppose that Example.com received export approval to issue step-up server certs, and they also decided that they'd prefer to have a third root specifically for object signing. The next version of the code might look like this:

In certdb.h: #define CERT_DB_CONTENT_VERSION 2

In certinit.c:

static char example_com_server_ca[] = "....";
static char example_com_individual_ca[] = "....";
static char example_com_objsign_ca[] = "....";
static certInitEntry initialcerts[] = {
    {
        example_com_server_ca,
        "Example.com Server CA",
        { DEFAULT_TRUST_FLAGS | CERTDB_GOVT_APPROVED_CA, 0, 0 },
        1,
        certUpdateAdd,
        { 0, 0, 0 }
    },
    {
        example_com_server_ca,
        "Example.com Server CA",
        { DEFAULT_TRUST_FLAGS | CERTDB_GOVT_APPROVED_CA, 0, 0 },
        2,
        certUpdateAddTrust, 
        { CERTDB_GOVT_APPROVED_CA, 0, 0 }
    },
    {
        example_com_individual_ca,
        "Example.com Individual CA",
        { 0, DEFAULT_TRUST_FLAGS, 0 },
        1,
        certUpdateAdd,
        { 0, 0, 0 }
    },
    {
        example_com_individual_ca,
        "Example.com Individual CA",
        { 0, DEFAULT_TRUST_FLAGS, 0 },
        2,
        certUpdateRemoveTrust,
        { 0, 0, DEFAULT_TRUST_FLAGS }
    },
    {
        example_com_objsign_ca,
        "Example.com Code Signing CA",
        { 0, 0, DEFAULT_TRUST_FLAGS },
        2,
        certUpdateAdd,
        { 0, 0, 0 }
    }
};

One dimensional

This scheme -- which admittedly grew over time -- worked when there was a one-dimensional sequence of the Netscape client products. Unfortunately, this scheme is not going to work if NSS is used in multiple divergent clients. In particular, we're afraid someone will write a program based on NSS, add their own root cert to their code (incrementing the database content version number) and release that program. Anyone else who makes a program with a similarly-numbered change (including future versions of Netscape, iPlanet, or mozilla.org programs) would find that their root trust information would not update properly. The only way this would work is if we all coordinated database content version numbers, which I don't think will be workable, especially in the long run.

Going forward

Luckily, we had already been planning on moving to a better system. We would like to move all sources of data -- the local database, ldap servers, these builtins, etc. -- to "data-only" PKCS#11 modules. This is in fact why we wrote the "Cryptoki Framework" in lib/ckfw. Different products could release small modules with their own trusted roots, and users could install any number of these from various sources.

We have defined a PKCS#11 "trust object" definition. The current code does not yet make use of "trust objects," but adding that soon will allow the code to use these external data modules even before the databases are replaced.