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.