NSS Sample Code Sample1

NSS Sample Code 1: Key Generation and Transport Between Servers.

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

#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 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;
}