mozilla
Your Search Results

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

    Document Tags and Contributors

    Contributors to this page: Sheppy, kwilson
    Last updated by: Sheppy,