NSS Library Init

From MozillaWiki
Jump to: navigation, search

NSS Library Initialization

Problem

NSS was designed as a library that a single application would use. The application would control how NSS was initialized and configured. An application would initialize NSS early before any libraries that used NSS could run. NSS initialization is idempotent, so system libraries could initialize NSS successfully after the application had already initialized NSS. Then, whether called by the application or by system libraries, NSS would use the application's configuration for certificates and trust. If NSS wasn't initialized by the application before it was initialized by system libraries, NSS would use the configuration files specified by the system libraries. Libraries would typically detect the case where NSS was already initialized before they used it, and would remember that, and would not shut NSS down when they were shut down.

There are several problems with that design:

  1. Like initialization, NSS shutdown is global over the entire application, and is forceful. Consequently, if the library shuts down NSS without the application's knowledge, and the application makes subsequent calls to NSS functions, those calls would experience errors or even crashes. Libraries can reduce this risk by checking to see if NSS is already initialized when they start, and if it was, then do not attempt to shut down NSS later when they are finished. However, this approach still has problems in cases where the library has initialized NSS before the application.
  2. In the same way, if the application shuts NSS down without the library knowing, they library may experience errors or crashes.
  3. If the library needs different databases to do its functions than those that the application opened, the library won't be able to access them.
  4. If the library initializes NSS first, and it chose different databases than the application wants, or it initializes the databases read-only but the application needs read-write access, the application won't be able to access the databases it desires in the desired fashion.
  5. If the application or library have different lists of trusted CA's, the first initializer's list wins.
  6. Multiple libraries may be using NSS, multiplying the problems.

How we initially intended to solve these problems

We intended to go to a single user/machine configuration for most applications and libraries, where all applications open the same set of databases for a particular user and machine. This configuration would not be in any one application's specific directory of application configuration files, but would be part of the system. One problem with this approach is that not all NSS applications run on systems which will have a 'system configured' NSS. In addition, there are still cases where the user may want to keep multiple different configurations for testing (Mozilla profiles for example). Finally, some users only need read-only access to the NSS configuration, but other users (like Firefox, or Thunderbird) need read-write access. This means if a read/only library initializes first, then a read/write application in that same address space will not be able to update the database. (what?? --MisterTLS 21:48, 1 September 2009 (UTC) I've changed application->library. This is a real issue that I believe sun reported. PAM calls pam-ldap which initializes NSS read only. now FF and TB can't get to their databases.)

Restrictions on any future solution

1) NSS must maintain binary compatibility. Applications should not become more broken as a result of linking with this new functionality. This means the existing shutdown should shutdown NSS in the same way that it has in the past.

The existing shutdown will close down all NSS internal references to object and free up internal lists. It is possible that the application may still hold references to NSS objects (such as slots, keys, or certs that NSS has returned to it) in its address space. NSS will shutdown all slots that do not have outstanding object references to them. If NSS cannot shutdown all slots, it will return an error. At this point NSS is 'shutdown', but it will not be able to initialized again until all those outstanding references are freed.

Few applications depend on being able to shutdown with a single NSS_Shutdown call, but there are some that do, usually applications which have some sort of dynamic profile switching code. Even though these apps are few, they must still continue to work.(Do you mean continue to fail? --MisterTLS 21:48, 1 September 2009 (UTC) no, I mean continue to work. Applications that call the base NSS_InitXXX expect it to be idempotent. It will continue to be. Applications expect to call NSS_Shutdown() and be able to switch profiles. This will continue to work as well. This last one we can discuss. It may make more sense for NSS_Shutdown to work as NSS_ShutdownContext(NULL) does. ) Caveat: Note that today, if there is a library using NSS and holding references, such profile switching will not work in any of the scenarios.

2) NSS still maintains a single 'trust domain' in which certificates are verified. In a single process, NSS has the ability to process certificates in the context of any one of several independent trust domains, but many of NSS's existing API functions do not allow a trust domain to be explicitly specified.

Support for explicit trust domains has long been on the NSS developers' 'wish list' but has not been implemented because there has been little or no demand from products that use NSS. Many uses of explicit trust domains may be better handled by the use of certificate policies. The primary use case for explicit trust domains currently identified is in virtual hosting services that service multiple customers through a single process web server.

Proposal

Provide a new NSS call to initialize NSS: NSS_InitContext(). This call would be made by system libraries that use NSS. NSS_InitContext() would take the same parameters as NSS_Init()*, but it will return a context that the library would save. This context would be passed to NSS_ShutdownContext(). The semantics and interaction of these initialization functions are as follows:

1. Multiple NSS_InitContext() calls are allowed. Each call will get it's own context. NSS will keep track of NSS_InitContext() calls. (why? --MisterTLS 22:00, 1 September 2009 (UTC) see NSS_ShutdownContext) If the database referenced by a given NSS library init call has not been opened, NSS_InitContext() will open that database in a new slot.

2. NSS_Init will continue to function as it does today with the following exception: while multple NSS_Init calls are idempotent, the first NSS_Init() called after one or more NSS_InitContext() calls will function just as NSS_InitContext() does with respect opening new databases. In addition, the 'main' database from NSS_Init() will be the database returned by PK11_GetInternalKeySlot().

3. NSS_ShutdownContext() can be called once per context. If NSS_ShutdownContext is called a second time on the same context, an error is returned. NSS_ShutdownContext() will shut down NSS if there are no more active contexts returned by NSS_InitContext() and NSS_Init() has not been called.

4. An application that initialized NSS with NSS_Init() can call NSS_LibraryShutdown() with NULL. This will close out the NSS_Init() call, but will only shutdown NSS if all the active contexts are also closed. (Note that NSS_Init() is still idempotent. This means that multiple calls to NSS_Init() before an NSS_Shutdown or and NSS_LibraryShutdown will still only result in a single NSS_Init call as far as shutdowns are concerned).

5. NSS_Shutdown() will operate as it does today. NSS will completely shutdown, all active contexts will be closed.

Signature for the new functions:

typedef struct NSSInitContextStr NSSInitContext; /* opaque */
typedef struct NSSInitStringStr NSSInitString;
struct NSSInitStringStr {
   int len; /* must be set to the length of NSSInitString. future versions.
             * of NSS may allow longer versions of this context, but need to
             * work with existing apps. */
   PRBool passwordRequired;
   int    minimumPasswordLength;
   char * manufactureID;           /* variable names for strings match the */
   char * libraryDescription;      /*   parameter name in softoken */
   char * cryptoTokenDescription;
   char * dbTokenDescription;
   char * FIPSTokenDescription;
   char * cryptoSlotDescription;
   char * dbSlotDescription;
   char * FIPSSlotDescription;
};


NSSInitContext *NSS_InitContext(const char *configdir,
       const char *certPrefix, const char *keyPrefix,
       const char *secmodName, NSSInitStrings *initStrings, PRUint32 flags);
SECStatus NSS_ShutdownContext(NSSInitContext *);


Comparison of the Current NSS Semantic and this proposal

Current NSS Semantic

Scenario 1.

  1. Application calls NSS_Init(). NSS_Init uses configdir to decide what PKCS #11 modules to load and what databases softoken opens.
  2. Library then calls NSS_Init(). Library ends up using whatever application already opened (in general, this ok, it make sense the the application controls what databases are used by all libraries it uses).
    • at this point only the application specified databases are open
  3. Library noticed that NSS was initializied already and does not call shutdown (hopefully).
  4. Application calls shutdown. NSS shuts down. Note, if the library had not reached '3' above and continues to use NSS, BadThings(tm) happen.

Scenario 2.

  1. Library calls NSS_Init(). NSS is opened based on the library's default configuration (needed because the library may be used in a non-NSS app.
  2. Application calls NSS_Init(). -- none of the application config directories are opened because NSS has already been initialized.
    • at this point only the library specified databases are open
  3. Library calls NSS_Shutdown() because the library did not see any users of NSS_Init(). NSS shuts down. The application is hosed if it tries to use NSS.
  4. Application calls shutdown. NSS shuts down (if it wasn't already shutdown from step 3). Again BadThings happen if the library didn't shutdown at step 3 and continues to use NSS.


Proposal

Scenario 1.

  1. Application calls NSS_init() - Same as Scenario 1 step 1 'Current Semantic'.
  2. Library calls NSS_InitContext() - Application databases remain open, In addition those databases specified by the Library (that aren't already the same as those by the Application database) are opened as all. These are now visible to anyone making NSS calls.
    • at this point the union of the databases specified by the library and the application are opened
  3. Library shutdowns calling NSS_ShutdownContext(). - NSS_ShutdownContext 'closes' the NSSInitContext and decrements the library open count. It notices that someone has called NSS_Init, so NSS is not shutdown yet. (if someone had not called NSS_Init, and the library open count had gone to zero, this call will shut down NSS).
  4. Application calls:
    1. NSS_Shutdown() - Same effect as Scenario 1 step 4 today (NSS Shutdowns, if the library is open and still using NSS, it's hosed).
    2. NSS_ShutdownContext(NULL) - NSS does not shutdown if there is any libraries open, but the internal nss_is_init flag that NSS_Init uses for idempotency is turned off (a future NSS_Init will try to open new databases). If all the libraries are closed, NSS shuts down normally.

Scenario 2.

  1. Library calls NSS_InitContext(). NSS is opened same as Scenario 2 step 1 'Current Semantic'. Note: that the internal nss_is_init flag will not be set, the library open count is incremented and a context is returned.
  2. Application calls NSS_Init() - databases in the application's config that were not already opened are now opened. (of course the internal nss_is_init flag will now be set).
    • at this point the union of the databases specified by the library and the application are opened
  3. Library shuts down calling NSS_ShutdownContext(). Same as scenario 1 step 3 'Proposal'.
  4. Application calls a shutdown function: same as scenario 1 step 4 'Proposal'.

Note 1: NSS_Init and NSS_Shutdown function like the do today. Applications that use them can depend on them just like the do today.

Note 2: The only crashing scenario in the proposal is the application calling NSS_Shutdown on a library (which is the same as we already have today). This can be fixed in the application but using the other shutdown function.

FAQ

  1. Shouldn't the NSSInitContext be passed to functions like PK11_GetBestSlot() or CERT_VerfiyCertificate()?
    • No. First, changing these signatures would not be allowed under the NSS binary compatibility guarantee. Second, the NSSInitContext is only a way to match up NSS_InitContext and NSS_ShutdownContext calls to prevent multiple libraries from prematurely closing NSS on each other.
  2. But what if you need to have differently configured trust and certs for different subsystems?
    • That problem is explicitly not being solved by this proposal. So far I've seen one convincing use of trust domains (that is a single process virtual hosting environment). Most proposed scenarios for using multiple trust domains are really better handled using certificate policy. Using a different root cert for each kind of access tends not to scale well.
    • That being said, adding trust domain support remains an option in the future. In all these cases, however, it's clear that trust domain support should be separate for NSS_Initialization. If you have multiple trust domains, you will most certainly want to support them in the context of a single NSS_Initialization.
    • Finally, in most cases, a library using NSS in the context of an application that is already using NSS, should use the configuation of the application anyway. User's don't view the combination as a separate entity, but a single application. When the user configures his certs in thunderbird, he will be surprised if those certs aren't available to his ldap library, for instance.
    • In short NSSInitContexts are not meant to be replacement for trust domains.
  3. What happens if NSS_Init is called more than once?
    • If NSS_Init is called more than once (without an intervening NSS_Shutdown), the second NSS_Init returns SECSuccess without doing anything, just as it does today. This is to keep current applications which depend on this behavior from breaking.
  4. What happens if I only call NSS_InitContext()?
    • NSS is fully initialized, and will stay initialized at least until you call NSS_ShutdownContext(), or someone calls NSS_Shutdown().
  5. Why does NSS_Shutdown() always shutdown, rather than just shutdown the NSS_Init side?
    • Some applications (at least in the past), call NSS_Shutdown() to 'switch profiles'. In order for this to work, NSS_Shutdown() must try to shutdown NSS completely.
    • NOTE: If some library attached to this application is actively using NSS at the time (open SSL connection, holding cert, key, or slot references, etc.), NSS_Shutdown() does not completely shutdown today. In light of this fact, it may be better to treat NSS_Shutdown() with the semantics described for NSS_ShutdownContext(NULL). I believe I can be talked into this semantic, but I wrote the proposal for the most conservative position on binary compatibility.
  6. What happens if you call a PKCS #11 module which has already been initialized?
    • When a someone attempts to initialize a PKCS #11 module a second time, the PKCS #11 module will return CKR_LIBRARY_ALREADY_INITIALIZED. At this point one of two things happen:
      1. If the PKCS #11 module supports the NSS 'add new slot' protocol, then NSS will add a new slow with the new configuration.
      2. For most modules we will move on. The requested module is already initialized and loaded in the trust domain, there is no further work needed to use it.
  7. What happens if different users initialize NSS with differing views of FIPS?
    • FIPS is really a attribute of the slot. NSS can have different slots open in different 'FIPS' states. This is rare today, it only happens when NSS switches from FIPS to non-FIPS. During the switch the old slots stay around until all their outstanding references go way.
    • The 'overall' reflection of FIPS would be controlled by the 'application' (the first caller of NSS_Init). In practice, however, you are not really in FIPS mode unless all your slots are in FIPS mode.