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
We will add message protection (encryption and MACing) examples to this program in the future.
Sample Code
#include <iostream.h> #include "pk11func.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; }