.. _mozilla_projects_nss_nss_sample_code_nss_sample_code_sample1:

NSS Sample Code Sample1
=======================

.. _nss_sample_code_1_key_generation_and_transport_between_servers.:

`NSS Sample Code 1: Key Generation and Transport Between Servers. <#nss_sample_code_1_key_generation_and_transport_between_servers.>`__
---------------------------------------------------------------------------------------------------------------------------------------

.. container::

   This is an example program that demonstrates how to do key generation and transport between
   cooperating servers.  This program shows the following:

   -  RSA key pair generation
   -  Naming RSA key pairs
   -  Looking up a previously generated key pair by name
   -  Creating AES and MAC keys (or encryption and MAC keys in general)
   -  Wrapping symmetric keys using your own RSA key pair so that they can be stored on disk or in a
      database.

      -  As an alternative to TOKEN symmetric keys

      -  As a way to store large numbers of symmetric keys

   -  Wrapping symmetric keys using an RSA key from another server
   -  Unwrapping keys using your own RSA key pair

   | The main part of the program shows a typical sequence of events for two servers that are trying
     to extablish a shared key pair.
   | We will add message protection (encryption and MACing) examples to this program in the future.

.. _sample_code:

`Sample Code <#sample_code>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. container::

   .. code:: notranslate

      #include <iostream.h>
      #include "pk11pub.h"
      #include "keyhi.h"
      #include "nss.h"

      // Key management for keys share among multiple hosts
      //
      // This example shows how to use NSS functions to create and
      // distribute keys that need to be shared among multiple servers
      // or hosts.
      //
      // The management scheme assumes that one host is PRIMARY.  It
      // generates the secret keys that will be used by all participating
      // hosts.  The other hosts (SECONDARY) request keys from the
      // primary host. As an alternative, new keys may be sent to the
      // current set of SECONDARY hosts when they are generated by the
      // PRIMARY.  In this case, the PRIMARY maintains a list of the
      // secondary hosts.
      //
      // The sequence of events is:
      // 1. The primary host generates a new symmetric key.  This key
      //    may be used for an encryption mechanism (DES or AES) or for
      //    integrity (MD5_HMAC or SHA1_HMAC). This key needs to be
      //    permanent, since it may be used during several runs of the
      //    server. (Currently NSS doesn't store persistant keys.  Steps
      //     1a through 1x show how to do this).
      //   1a. The primary host generates an RSA keypair that will be used
      //       store keys locally.
      //   1b. The primary host wraps the newly generated key using the
      //       RSA key and stores the wrapped key data in a local file.
      //   1c. The primary host unwraps the key using the RSA key each time
      //       access to the key is required, such as at server startup.
      // 2. The secondary host generates an RSA keypair that will be used
      //    to transport keys between the primary host and itself. This
      //    key needs to exist long enough to be used to process the
      //    response to a key transport request that is made to the primary
      //    server. The example here shows how to create a permanent (token)
      //    RSA key for this purpose. (This key will also be used for
      //    storage of the keys, since NSS does not support permanent symmetric
      //    keys at the current time.)
      // 3. The secondary host sends its RSA public key to the primary host as
      //    part of a request for a particular key, or to be added to a list
      //    of secondary hosts.
      // 4. The administrator of the primary host verifies that the RSA key
      //    that was received belongs to a valid secondary host.  The adminstrator
      //    may do this by checking that the key was received in a signed email
      //    message, or by checking a digest value with the adminstrator of the
      //    secondary host.  [Need support for digest check values]
      // 5. The primary host exports (wraps) the symmetric key using the
      //    secondary host's RSA key.  The wrapped value is sent back to
      //    the secondary host.
      // 6. The administrator of the secondary host verifies that the wrapped
      //    key data came from the primary host. The same methods outlined
      //    in step 4 may be used here.
      // 7. The secondary host unwraps the key using its own RSA private key.
      //    NOTE: currently NSS does not support permanent symmetric keys.
      //    The secondary host may store the wrapped value that was received
      //    from the primary in a file, and unwrap it each time the key is required
      //    (such as at server startup).

      // NSS actually has some support for permanent symmetric keys. However this
      // example will need to be modified somewhat in order to demonstrate it.

      // Utility function to print hex data
      static void
      printBuffer(unsigned char *digest, unsigned int len)
      {
        int i;

        cout << "length: " << len << endl;
        for(i = 0;i < len;i++) printf("%02x ", digest[i]);
        cout << endl;
      }

      // XXX Data protection
      //  - takes an input buffer, applies the encryption
      //    and MAC, and generates a buffer with the result.
      //  - the application sends or uses the result (possibly
      //    after base64 encoding it.

      //
      // Server - an instance of a server that is part of a
      //   cluster of servers that are sharing a common set
      //   of encryption and MACing keys.
      //
      class Server
      {
      public:
        // Initializes the server instance. In particular, this
        // creates the key pair that is used for wrapping keys
        int Init();

        // Generates keys for encryption (AES) and MACing. The
        // wrapped keys are stored in data files.
        int GenerateKeys();

        // Gets the server's public key (wrapping key) to
        // send to another server. This becomes the input to
        // the ExportKeys method on the remote server.
        int ExportPublicKey(SECItem **pubKeyData);

        // Export the encryption and key using the key
        // provided. The key should come from another server
        // in the cluster. (The admin should verify this.)
        //
        // In this example, the server must be started to perform
        // this function (see Start())
        int ExportKeys(SECItem *pubKey, SECItem **wrappedEncKey,
                     SECItem **wrappedMacKey);

        // Import the keys received from another server in the
        // cluster. The admin should make sure the keys actually
        // came from the correct source.
        int ImportKeys(SECItem *wrappedEncKey, SECItem *wrappedMacKey);

        // Start the server, loading the encryption and MACing keys
        // from files
        int Start();

        // Shut down the server. (For completeness)
        int Shutdown();

        // Compare keys in two server instances. Use this in the
        // example to make sure the keys are transferred correctly.
        // This will not work in real life!
        //
        // The servers must be started
        int CompareKeys(Server *peer);

        // Create a server - the name distiguish the keys in the
        // shared database in this example
        Server(const char *serverName);
        ~Server();

      private:
        int getPrivateKey(SECKEYPrivateKey **prvKey);
        int getPublicKey(SECKEYPublicKey **pubKey);
        int wrapKey(PK11SymKey *key, SECKEYPublicKey *pubKey, SECItem **data);

        // export raw key (unwrapped) DO NOT USE
        int rawExportKey(PK11SymKey *key, SECItem **data);

        char *mServerName;

        // These items represent data that might be stored
        // in files or in a configuration file
        SECItem *mWrappedEncKey;
        SECItem *mWrappedMacKey;

        // These are the runtime keys as loaded from the files
        PK11SymKey *mEncKey;
        PK11SymKey *mMacKey;
      };

      Server::Server(const char *serverName)
      : mServerName(0), mWrappedEncKey(0), mWrappedMacKey(0),
        mEncKey(0), mMacKey(0)
      {
        // Copy the server name
        mServerName = PL_strdup(serverName);
      }

      Server::~Server()
      {
        if (mServerName) PL_strfree(mServerName);
        if (mWrappedEncKey) SECITEM_FreeItem(mWrappedEncKey, PR_TRUE);
        if (mWrappedMacKey) SECITEM_FreeItem(mWrappedMacKey, PR_TRUE);
        if (mEncKey) PK11_FreeSymKey(mEncKey);
        if (mMacKey) PK11_FreeSymKey(mMacKey);
      }

      int
      Server::Init()
      {
        int rv = 0;
        SECKEYPrivateKey *prvKey = 0;
        SECKEYPublicKey *pubKey = 0;
        PK11SlotInfo *slot = 0;
        PK11RSAGenParams rsaParams;
        SECStatus s;

        // See if there is already a private key with this name.
        // If there is one, no further action is required.
        rv = getPrivateKey(&prvKey);
        if (rv == 0 && prvKey) goto done;

        rv = 0;

        // These could be parameters to the Init function
        rsaParams.keySizeInBits = 1024;
        rsaParams.pe = 65537;

        slot = PK11_GetInternalKeySlot();
        if (!slot) { rv = 1; goto done; }

        prvKey = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaParams,
                     &pubKey, PR_TRUE, PR_TRUE, 0);
        if (!prvKey) { rv = 1; goto done; }

        // Set the nickname on the private key so that it
        // can be found later.
        s = PK11_SetPrivateKeyNickname(prvKey, mServerName);
        if (s != SECSuccess) { rv = 1; goto done; }

      done:
        if (slot) PK11_FreeSlot(slot);
        if (pubKey) SECKEY_DestroyPublicKey(pubKey);
        if (prvKey) SECKEY_DestroyPrivateKey(prvKey);

        return rv;
      }

      int
      Server::GenerateKeys()
      {
        int rv = 0;
        SECKEYPublicKey *pubKey = 0;
        PK11SlotInfo *slot = 0;

        // Choose a slot to use
        slot = PK11_GetInternalKeySlot();
        if (!slot) { rv = 1; goto done; }

        // Get our own public key to use for wrapping
        rv = getPublicKey(&pubKey);
        if (rv) goto done;

        // Do the Encryption (AES) key
        if (!mWrappedEncKey)
        {
          PK11SymKey *key = 0;

          // The key size is 128 bits (16 bytes)
          key = PK11_KeyGen(slot, CKM_AES_KEY_GEN, 0, 128/8, 0);
          if (!key) { rv = 1; goto aes_done; }

          rv = wrapKey(key, pubKey, &mWrappedEncKey);

        aes_done:
          if (key) PK11_FreeSymKey(key);

          if (rv) goto done;
        }

        // Do the Mac key
        if (!mWrappedMacKey)
        {
          PK11SymKey *key = 0;

          // The key size is 160 bits (20 bytes)
          key = PK11_KeyGen(slot, CKM_GENERIC_SECRET_KEY_GEN, 0, 160/8, 0);
          if (!key) { rv = 1; goto mac_done; }

          rv = wrapKey(key, pubKey, &mWrappedMacKey);

        mac_done:
          if (key) PK11_FreeSymKey(key);
        }

      done:
        if (slot) PK11_FreeSlot(slot);

        return rv;
      }

      int
      Server::ExportPublicKey(SECItem **pubKeyData)
      {
        int rv = 0;
        SECKEYPublicKey *pubKey = 0;

        rv = getPublicKey(&pubKey);
        if (rv) goto done;

        *pubKeyData = SECKEY_EncodeDERSubjectPublicKeyInfo(pubKey);
        if (!*pubKeyData) { rv = 1; goto done; }

      done:
        if (pubKey) SECKEY_DestroyPublicKey(pubKey);

        return rv;
      }

      int
      Server::ExportKeys(SECItem *pubKeyData, SECItem **wrappedEncKey,
                         SECItem **wrappedMacKey)
      {
        int rv;
        CERTSubjectPublicKeyInfo *keyInfo = 0;
        SECKEYPublicKey *pubKey = 0;
        SECItem *data = 0;

        // Make sure the keys are available (server running)
        if (!mEncKey || !mMacKey) { rv = 1; goto done; }

        // Import the public key of the other server
        keyInfo = SECKEY_DecodeDERSubjectPublicKeyInfo(pubKeyData);
        if (!keyInfo) { rv = 1; goto done; }

        pubKey = SECKEY_ExtractPublicKey(keyInfo);
        if (!pubKey) { rv = 1; goto done; }

        // Export the encryption key
        rv = wrapKey(mEncKey, pubKey, &data);
        if (rv) goto done;

        // Export the MAC key
        rv = wrapKey(mMacKey, pubKey, wrappedMacKey);
        if (rv) goto done;

        // Commit the rest of the operation
        *wrappedEncKey = data;
        data = 0;

      done:
        if (data) SECITEM_FreeItem(data, PR_TRUE);
        if (pubKey) SECKEY_DestroyPublicKey(pubKey);
        if (keyInfo) SECKEY_DestroySubjectPublicKeyInfo(keyInfo);

        return rv;
      }

      int
      Server::ImportKeys(SECItem *wrappedEncKey, SECItem *wrappedMacKey)
      {
        int rv = 0;

        if (mWrappedEncKey || mWrappedMacKey) { rv = 1; goto done; }

        mWrappedEncKey = SECITEM_DupItem(wrappedEncKey);
        if (!mWrappedEncKey) { rv = 1; goto done; }

        mWrappedMacKey = SECITEM_DupItem(wrappedMacKey);
        if (!mWrappedMacKey) { rv = 1; goto done; }

      done:
        return rv;
      }

      int
      Server::Start()
      {
        int rv;
        SECKEYPrivateKey *prvKey = 0;

        rv = getPrivateKey(&prvKey);
        if (rv) goto done;

        if (!mEncKey)
        {
          // Unwrap the encryption key from the "file"
          // This function uses a mechanism rather than a key type
          // Does this need to be "WithFlags"??
          mEncKey = PK11_PubUnwrapSymKey(prvKey, mWrappedEncKey,
                       CKM_AES_CBC_PAD, CKA_ENCRYPT, 0);
          if (!mEncKey) { rv = 1; goto done; }
        }

        if (!mMacKey)
        {
          // Unwrap the MAC key from the "file"
          // This function uses a mechanism rather than a key type
          // Does this need to be "WithFlags"??
          mMacKey = PK11_PubUnwrapSymKey(prvKey, mWrappedMacKey,
                       CKM_MD5_HMAC, CKA_SIGN, 0);
          if (!mMacKey) { rv = 1; goto done; }
        }

      done:
        if (prvKey) SECKEY_DestroyPrivateKey(prvKey);

        return rv;
      }

      int
      Server::Shutdown()
      {
        if (mEncKey) PK11_FreeSymKey(mEncKey);
        if (mMacKey) PK11_FreeSymKey(mMacKey);

        mEncKey = 0;
        mMacKey = 0;

        return 0;
      }

      int
      Server::CompareKeys(Server *peer)
      {
        int rv;
        SECItem *macKey1 = 0;
        SECItem *macKey2 = 0;
        SECItem *encKey1 = 0;
        SECItem *encKey2 = 0;

        // Export each of the keys in raw form
        rv = rawExportKey(mMacKey, &macKey1);
        if (rv) goto done;

        rv = rawExportKey(peer->mMacKey, &macKey2);
        if (rv) goto done;

        rv = rawExportKey(mEncKey, &encKey1);
        if (rv) goto done;

        rv = rawExportKey(peer->mEncKey, &encKey2);
        if (rv) goto done;

        if (!SECITEM_ItemsAreEqual(macKey1, macKey2)) { rv = 1; goto done; }
        if (!SECITEM_ItemsAreEqual(encKey1, encKey2)) { rv = 1; goto done; }

      done:
        if (macKey1) SECITEM_ZfreeItem(macKey1, PR_TRUE);
        if (macKey2) SECITEM_ZfreeItem(macKey2, PR_TRUE);
        if (encKey1) SECITEM_ZfreeItem(encKey1, PR_TRUE);
        if (encKey2) SECITEM_ZfreeItem(encKey2, PR_TRUE);

        return rv;
      }

      // Private helper, retrieves the private key for the server
      // from the database.  Free the key using SECKEY_DestroyPrivateKey
      int
      Server::getPrivateKey(SECKEYPrivateKey **prvKey)
      {
        int rv = 0;
        PK11SlotInfo *slot = 0;
        SECKEYPrivateKeyList *list = 0;
        SECKEYPrivateKeyListNode *n;
        char *nickname;

        slot = PK11_GetInternalKeySlot();
        if (!slot) goto done;

        // ListPrivKeysInSlot looks like it should check the
        // nickname and only return keys that match.  However,
        // that doesn't seem to work at the moment.
        // BUG: XXXXX
        list = PK11_ListPrivKeysInSlot(slot, mServerName, 0);
        cout << "getPrivateKey: list = " << list << endl;
        if (!list) { rv = 1; goto done; }

        for(n = PRIVKEY_LIST_HEAD(list);
            !PRIVKEY_LIST_END(n, list);
            n = PRIVKEY_LIST_NEXT(n))
        {
          nickname = PK11_GetPrivateKeyNickname(n->key);
          if (PL_strcmp(nickname, mServerName) == 0) break;
        }
        if (PRIVKEY_LIST_END(n, list)) { rv = 1; goto done; }

        *prvKey = SECKEY_CopyPrivateKey(n->key);

      done:
        if (list) SECKEY_DestroyPrivateKeyList(list);

        return rv;
      }

      int
      Server::getPublicKey(SECKEYPublicKey **pubKey)
      {
        int rv;
        SECKEYPrivateKey *prvKey = 0;

        rv = getPrivateKey(&prvKey);
        if (rv) goto done;

        *pubKey = SECKEY_ConvertToPublicKey(prvKey);
        if (!*pubKey) { rv = 1; goto done; }

      done:
        if (prvKey) SECKEY_DestroyPrivateKey(prvKey);

        return rv;
      }

      int
      Server::wrapKey(PK11SymKey *key, SECKEYPublicKey *pubKey, SECItem **ret)
      {
        int rv = 0;
        SECItem *data;
        SECStatus s;

        data = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
        if (!data) { rv = 1; goto done; }

        // Allocate space for output of wrap
        data->len = SECKEY_PublicKeyStrength(pubKey);
        data->data = new unsigned char[data->len];
        if (!data->data) { rv = 1; goto done; }

        s = PK11_PubWrapSymKey(CKM_RSA_PKCS, pubKey, key, data);
        if (s != SECSuccess) { rv = 1; goto done; }

        *ret = data;
        data = 0;

      done:
        if (data) SECITEM_FreeItem(data, PR_TRUE);

        return rv;
      }

      // Example of how to do a raw export (no wrapping of a key)
      // This should not be used. Use the RSA-based wrapping
      // methods instead.
      int
      Server::rawExportKey(PK11SymKey *key, SECItem **res)
      {
        int rv = 0;
        SECItem *data;
        SECStatus s;

        s = PK11_ExtractKeyValue(key);
        if (s != SECSuccess) { rv = 1; goto done; }

        data = PK11_GetKeyData(key);

        *res = SECITEM_DupItem(data);
        if (!*res) { rv = 1; goto done; }

      done:
        return rv;
      }

      // Initialize the NSS library. Normally this
      // would be done as part of each server's startup.
      // However, this example uses the same databases
      // to store keys for server in the "cluster" so
      // it is done once.
      int
      InitNSS()
      {
        int rv = 0;
        SECStatus s;

        s = NSS_InitReadWrite(".");
        if (s != SECSuccess) rv = 1;  // Error

        // For this example, we don't use database passwords
        PK11_InitPin(PK11_GetInternalKeySlot(), "", "");

        return rv;
      }

      int
      main(int argc, char *argv[])
      {
        int rv;
        Server *server1 = 0;
        Server *server2 = 0;

        // Initialize NSS
        rv = InitNSS();
        if (rv) { cout << "InitNSS failed" << endl; goto done; }

        // Create the first "server"
        server1 = new Server("Server1");
        if (!server1 || server1->Init())
        {
          cout << "Server1 could not be created" << endl;
          rv = 1;
          goto done;
        }

        // Generate encryption and mac keys. These keys will
        // be used by all the servers in the cluster.
        rv = server1->GenerateKeys();
        if (rv) { cout << "GenerateKeys failed" << endl; goto done; }

        // Now that everything is ready, start server1. This loads
        // the encryption and MAC keys from the "files"
        rv = server1->Start();
        if (rv) { cout << "Cannot start server 1" << endl; goto done; }

        // Create a second server in the cluster. We will need
        // to transfer the keys from the first server to this
        // one
        server2 = new Server("Server2");
        if (!server2 || server2->Init())
        {
          cout << "Server2 could not be created" << endl;
          rv = 1; // Error
          goto done;
        }

        // Transfer the keys from server1
        {
          SECItem *wrappedEncKey = 0;
          SECItem *wrappedMacKey = 0;
          SECItem *pubKeyData = 0;

          // Get the public key for server 2 so that it can
          // be sent to server 1
          rv = server2->ExportPublicKey(&pubKeyData);
          if (rv) { cout << "ExportPublicKey failed" << endl; goto trans_done; }

          // Send the public key to server 1 and get back the
          // wrapped key values
          rv = server1->ExportKeys(pubKeyData, &wrappedEncKey, &wrappedMacKey);
          if (rv) { cout << "ExportKeys failed" << endl; goto trans_done; }

          // Print - for information
          cout << "Wrapped Encryption Key" << endl;
          printBuffer(wrappedEncKey->data, wrappedEncKey->len);
          cout << "Wrapped MAC Key" << endl;
          printBuffer(wrappedMacKey->data, wrappedMacKey->len);

          // Import the keys into server 2 - this just puts the wrapped
          // values into the "files"
          rv = server2->ImportKeys(wrappedEncKey, wrappedMacKey);
          if (rv) { cout << "ImportKeys failed" << endl; goto trans_done; }

        trans_done:
          if (wrappedEncKey) SECITEM_FreeItem(wrappedEncKey, PR_TRUE);
          if (wrappedMacKey) SECITEM_FreeItem(wrappedMacKey, PR_TRUE);
          if (pubKeyData) SECITEM_FreeItem(pubKeyData, PR_TRUE);
        }
        if (rv) goto done;

        // Start server 2 - this unwraps the encryption and MAC keys
        // so that they can be used
        rv = server2->Start();
        if (rv) { cout << "Cannot start server 2" << endl; goto done; }

        // List keys in the token - informational
        {
          PK11SlotInfo *slot = 0;
          SECKEYPrivateKeyList *list = 0;
          SECKEYPrivateKeyListNode *n;

          slot = PK11_GetInternalKeySlot();
          if (!slot) goto list_done;

          cout << "List Private Keys" << endl;

          list = PK11_ListPrivKeysInSlot(slot, 0, 0);
          if (!list) goto list_done;

          for(n = PRIVKEY_LIST_HEAD(list);
              !PRIVKEY_LIST_END(n, list);
              n = PRIVKEY_LIST_NEXT(n))
          {
            char *name;

            name = PK11_GetPrivateKeyNickname(n->key);
            cout << "Key: " << name << endl;
          }
        list_done:
          if (slot) PK11_FreeSlot(slot);
          if (list) SECKEY_DestroyPrivateKeyList(list);

          cout << "Done" << endl;
        }

        // Let's see if the keys are the same
        rv = server1->CompareKeys(server2);
        if (rv) { cout << "Key Comparison failed" << endl; }

        server1->Shutdown();
        server2->Shutdown();

      done:
        if (server1) delete server1;
        if (server2) delete server2;

        NSS_Shutdown();

        return rv;
      }