Key Generation and Transport Between Servers
NSS Sample Code: 1
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>
using namespace std;
#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)
{
unsigned 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;
do {
// 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) break;
rv = 1;
// These could be parameters to the Init function
rsaParams.keySizeInBits = 1024;
rsaParams.pe = 65537;
slot = PK11_GetInternalKeySlot();
if (!slot) break;
prvKey = PK11_GenerateKeyPair(slot,
CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaParams,
&pubKey, PR_TRUE, PR_TRUE, 0);
if (!prvKey) break;
// Set the nickname on the private key so that it
// can be found later.
s = PK11_SetPrivateKeyNickname(prvKey, mServerName);
if (s != SECSuccess) break;
rv = 0;
} while (0);
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) return 1;
// Get our own public key to use for wrapping
rv = getPublicKey(&pubKey);
if (rv) goto done;
// Do the Encryption (AES) key
if (!rv && !mWrappedEncKey)
{
// The key size is 128 bits (16 bytes)
PK11SymKey *key = PK11_KeyGen(slot, CKM_AES_KEY_GEN, 0, 128/8, 0);
rv = key ? wrapKey(key, pubKey, &mWrappedEncKey) : 1;
if (key) PK11_FreeSymKey(key);
}
// Do the Mac key
if (!rv && !mWrappedMacKey)
{
// The key size is 160 bits (20 bytes)
PK11SymKey *key = PK11_KeyGen(slot, CKM_GENERIC_SECRET_KEY_GEN, 0, 160/8, 0);
rv = key ? wrapKey(key, pubKey, &mWrappedMacKey) : 1;
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()
{
SECKEYPrivateKey *prvKey = 0;
int rv = getPrivateKey(&prvKey);
if (!rv && !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);
rv = mEncKey ? 0 : 1;
}
if (!rv && !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);
rv = mMacKey ? 0 : 1;
}
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)
{
PK11SlotInfo *slot = PK11_GetInternalKeySlot();
if (!slot) return 0;
// 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
SECKEYPrivateKeyList *list = PK11_ListPrivKeysInSlot(slot, mServerName, 0);
cout << "getPrivateKey: list = " << list << endl;
if (!list) return 1;
// search for the key whose nickname matches the server's name
int rv = 1; // assume will not succeed
for (SECKEYPrivateKeyListNode *n = PRIVKEY_LIST_HEAD(list);
!PRIVKEY_LIST_END(n, list); n = PRIVKEY_LIST_NEXT(n))
{
char *nickname = PK11_GetPrivateKeyNickname(n->key);
if (PL_strcmp(nickname, mServerName) == 0) {
rv = 0; // success
*prvKey = SECKEY_CopyPrivateKey(n->key);
break;
}
}
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;
SECStatus s;
SECItem *data = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
if (!data) { return 1; }
do {
// Allocate space for output of wrap
data->len = SECKEY_PublicKeyStrength(pubKey);
data->data = new unsigned char[data->len];
if (!data->data) { rv = 1; break; }
s = PK11_PubWrapSymKey(CKM_RSA_PKCS, pubKey, key, data);
if (s != SECSuccess) { rv = 1; break; }
*ret = data;
data = 0;
} while (0);
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)
{
if (SECSuccess != (PK11_ExtractKeyValue(key))) return 1;
SECItem *data = PK11_GetKeyData(key);
if (!data) return 1;
*res = SECITEM_DupItem(data);
return *res ? 0 : 1;
}
// 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()
{
SECStatus s = NSS_InitReadWrite(".");
if (s != SECSuccess) return 1; // Error
// For this example, we don't use database passwords
s = PK11_InitPin(PK11_GetInternalKeySlot(), "", "");
return (s == SECSuccess) ? 0 : 1;
}
// Transfer the keys from server1 to server2
static int TrasferKeys(Server *server1, Server *server2)
{
SECItem *wrappedEncKey = 0;
SECItem *wrappedMacKey = 0;
SECItem *pubKeyData = 0;
int rv;
do {
// 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; break; }
// 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; break; }
// 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; break; }
} while (0);
if (wrappedEncKey) SECITEM_FreeItem(wrappedEncKey, PR_TRUE);
if (wrappedMacKey) SECITEM_FreeItem(wrappedMacKey, PR_TRUE);
if (pubKeyData) SECITEM_FreeItem(pubKeyData, PR_TRUE);
return rv;
}
// List the keys in the token
static void ListTheKeys(PK11SlotInfo *slot)
{
if (!slot) return;
cout << "List Private Keys" << endl;
SECKEYPrivateKeyList *list = PK11_ListPrivKeysInSlot(slot, 0, 0);
if (list) {
for (SECKEYPrivateKeyListNode *n = PRIVKEY_LIST_HEAD(list);
!PRIVKEY_LIST_END(n, list); n = PRIVKEY_LIST_NEXT(n))
{
char *name = PK11_GetPrivateKeyNickname(n->key);
cout << "Key: " << name << endl;
}
}
if (slot)
PK11_FreeSlot(slot);
if (list)
SECKEY_DestroyPrivateKeyList(list);
cout << "Done" << endl;
}
int
main(int argc, char *argv[])
{
int rv;
Server *server1 = 0;
Server *server2 = 0;
do {
// Initialize NSS
rv = InitNSS();
if (rv) { cout << "InitNSS failed" << endl; break; }
// Create the first "server"
server1 = new Server("Server1");
if (!server1 || server1->Init()) {
cout << "Cannot create server 1" << endl; rv = 1; break;
}
// 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; break; }
// 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; break; }
// 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 << "Cannot create server 2" << endl; rv = 1; break;
}
// Transfer the keys from server1 to server2
rv = TrasferKeys(server1, server2);
if (rv) break;
// 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; break; }
// List keys in the token - informational
ListTheKeys(PK11_GetInternalKeySlot());
// Let's see if the keys are the same
rv = server1->CompareKeys(server2);
if (rv) { cout << "Key Comparison failed" << endl; }
server1->Shutdown();
server2->Shutdown();
} while (0);
if (server1) delete server1;
if (server2) delete server2;
NSS_Shutdown();
return rv;
}