This document was updated on 30 March 2014.
Key management is essential for data at rest on disk and tape drives. Data at rest (e.g., financial, medial records) has to be stored for years. Stored in encrypted form the only way to access the real data is by reliably locating its associated key. For data in transit on computer networks long term data retention is not a requirement since keys are regularly replaced after a set amount of data or time. Data in transit applications can use easy modification of key meta data (e.g., change in key state), key association with certificates, and a fast key find. In addition, a flexible keystore should be able to handle application specific data stored near its keys. We have designed and built a powerful keystore as a building block for an overall solution to key management.
Requirements for a keystore
P6R’s keystore supports key management applications by addressing the following requirements:
- Support both client and server applications with a software solution that can be used by itself or included as part of an appliance.
- Support the storage of all types of keys, certificates, key — certificate relationships (e.g., key chains), and non-key, opaque, application specific data (e.g., passwords).
- Provide a hierarchical naming structure to help applications organize keystore contents.
- Enforce that all data in the keystore is encrypted and that the entire contents of the keystore is signed to prevent unauthorized modification (e.g., encryption of key material alone will not prevent the deletion of a key.)
- Provide a powerful key look up mechanism that supports all types of queries (e.g., find all keys with ciphers like “AES” which would include “AES CFB”, “AES CBC” in the reesults).
- Support the NIST Special Publication 800-130: “A Framework for Designing Cryptographic Key Management Systems” which defines extensive key meta data (e.g., includes a set of key states, use, and dates).
- Allow arbitrary association of objects stored in a keystore (e.g., associate a public key with its matching private key).
- Allow an application to associate any number of application specific attributes to an entry in a keystore (e.g., set the date of when a key “will expire”). Also allow queries to find entries in the keystore based on these attribute name value pairs (e.g., find all keys that “will expire” in 30 days).
How we satisfied the requirements
- P6R’s keystore is part of P6R’s Server Development Platform which provides rich multi-platform client and server functionality. It is also part of our KMIP Client SDK. All P6R software is written in C++ but the platform also provides bindings for Perl, PHP, and Python.
- The basic keystore API is as follows:
initialize( P6KEYSTOREFLAGS flags, p6ISymmetricCrypto* pEncryptKey, P6SIGNHMAC signAlg, p6ICryptoKey* pSignKey ); openSigned( const P6WCHAR* pPath, const P6WCHAR* pKeystoreName ); open( const P6WCHAR* pURI ); getKey( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICryptoKey** pKey ); getCertificate( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICert** pCert ); getCertificateChain( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICert** pCertChain, P6UINT32 numChain, P6UINT32* pNumWritten ); getBlob( const P6WCHAR* pNamespace, const P6WCHAR* pName, P6BSTR* pBlob ); getLinkByGUID( P6UUID* pUUID, P6KEYSTORE_LINK* pLink ); setKey( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICryptoKey* pKey, p6ICert** pCertChain, P6UINT32 numChain ); setCertificate( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICert* pCert ); setBlob( const P6WCHAR* pNamespace, const P6WCHAR* pName, P6BSTR blob ); createLink( P6UUID* pSourceUUID, P6UUID* pTargetUUID, const P6WCHAR* pLinkType, const P6WCHAR* pMetaData, P6UUID* pUUID ); deleteKey( const P6WCHAR* pNamespace, const P6WCHAR* pName ); deleteCertificate( const P6WCHAR* pNamespace, const P6WCHAR* pName ); deleteBlob( const P6WCHAR* pNamespace, const P6WCHAR* pName ); deleteItem( P6UUID* pUUID ); updateKeyMetaData( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICryptoKey* pKey ); enumEntries( P6KEYSTORE_TYPE byType, const P6WCHAR* pNamespace, p6IEnumKeystore** ppIterator ); addAttribute( P6UUID* pUUID, P6KEYSTORE_ATTRIBUTE newAttrib ); updateAttribute( P6UUID* pUUID, P6KEYSTORE_ATTRIBUTE changeAttrib ); getAttribute( P6UUID* pUUID, P6KEYSTORE_ATTRIBUTE* pAttrib ); deleteAttribute( P6UUID* pUUID, const P6WCHAR* pName ); enumAttributes( P6UUID* pUUID, p6IEnumKeystoreAttrib** ppEnum );
Notice from the functions listed above the getBlob / setBlob calls. These functions provide support for management of non-key, opaque application specific data that is stored just like any key or certificate (i.e., encrypted, in a hierarchical namespace). Also the p6ICryptoKey** pKey parameter to the getKey / setKey calls is P6R’s key component that supports both symmetric and asymmetric keys. - In the getKey and setKey calls listed above notice the pNamespace and pName parameters. The pName parameter is a logical name for the key while the pNamespace provides a single level of hierarchy for all keys. This same naming scheme is used for certificates and blobs. One example of the use of namespace could be by protocol such as “SNMP”, “HTTPS”, and “ESMTP”. In addition to this one level of hiearchy, an application can define multiple keystores each representing another level of hierarchy or domain. This is supported by the openSigned() function’s path variable which defines where the keystore is saved in the file system.
- Again looking at the initialize function listed above, notice the pEncryptKey and pSignKey required parameters. The application decides what type of cipher is used to encrypt the key material (and certificate and blob data) that is stored in the keystore. The keystore software does the encryption for the application but the application must provide the same two keys each time it opens the keystore.
- If an application uses the openSigned() call, then the pSignKey parameter from the initialize() call is used to sign the entire keystore when it is closed and that signature is verified on keystore open. The openSigned() call places the keystore in a locally created SQLite (single file) database. However, if an application needs to use a larger database or one on a remote machine, then it can make the open() call. The pURI parameter to the open() call defines the database to store the keystore. For example, to use Postgresql an application just calls:
open("postgresql8://postgres/postgres5678/server.example.com//5432/keystore_name");
However, when using the open() call the keystore wide signature is no longer supported.
- The attribute functions: addAttribute(), updateAttribute(), getAttribute(), deleteAttribute(), and enumAttribute() allow CRUD functionality for all attributes associated with a entry in the keystore. These functions have a P6UUID parameter which is a GUID that all entries in the keystore contain and uniquely identify each entry. An attribute consists of a string name and can have one of three type of values: a string, an integer, or a time stamp. Attribute names are selected solely by the application.
- We believe that it is absolutely essential to make it easy to find any key in the keystore by a variety of parameters. Thus we have implemeted our keystore in a database allowing us to perform SQL queries to find different subsets of keys. Our keystore’s schema has a column for each important key meta data (e.g., key state, key length, key’s cipher, key’s expected use — see the NIST document referenced above). We have also added this key meta data to the p6ICryptoKey component, allowing an application to get and set standard meta data. Thus once a key is extracted from the keystore it contains all its associated meta data available for the application to query or change.
- Extending P6R’s p6ICryptoKey component to hold the suggested key meta data from the NIST SP 800-130 document and allowing database look ups based on this meta data supports the NIST requirements. In addition, when a key and its associated meta data are stored into the keystore a signature is also saved into the same database row. This signature seals the key material with its meta data. Whenever a key is returned to a caller through the getKey() function this signature is first verified and if it fails that key is not returned.
- Any two entries in a keystore can be associated by creating a link object between the two. A link has a source, a target, and a type. For example, we could create a link between a key pair (the source being the public key and the target being its matching private key, with the type defined by the application as “Public KEY PAIR”). Using links an application can group any arbitrary set of objects together. Since links are treated as objects by the keystore they can also be searched using the functions enumLinkBySource(), enumLinkByTarget(), and enumLinkLikeType(). For example, using these search functions an application could find all links where a specific key is the source, or where the link type matches a SQL like query “%KEY PAIR%”.
- The enumByAttributeXXX() functions allow an application to find any key, certificate, and/or blob in the keystore just by an attributes name (e.g., find all entries that have the attribute “OWNER” or “Revocation Reason”) or by an attribute name value pair (e.g., find all entries that have attribute name “Revocation Reason” with value “Compromised”, or attribute name “Will Expire” with value time stamp 30 days from today). In all these queries, the application can specify the type of entries to search for (e.g., just keys or certificates) or to search across all entries in the keystore. All the query functions provided by our keystore return 0 to n keystore entries in an enumerator object.
The keystore has a special query interface allowing the following key lookup functions:
enumKeyBySize( P6UINT32 size, P6INT8 comparator, p6IEnumKeystore** ppIterator ); enumKeyByState( P6CRYPTOKEYSTATE state, p6IEnumKeystore** ppIterator ); enumKeyByCipher( P6CRYPTOCIPHER cipher, p6IEnumKeystore** ppIterator ); enumKeyByClass( P6CRYPTOKEYCLASS keyClass, p6IEnumKeystore** ppIterator ); enumKeyByExpired( P6TIME expired, p6IEnumKeystore** ppIterator ); enumKeyByRenewal( P6TIME expired, p6IEnumKeystore** ppIterator ); enumKeyBySizeByCipher( P6UINT32 size, P6INT8 comparitor, P6CRYPTOCIPHER cipher, p6IEnumKeystore** ppIterator ); enumKeyBySizeByClass( P6UINT32 size, P6INT8 comparitor, P6CRYPTOKEYCLASS keyClass, p6IEnumKeystore** ppIterator ); enumKeyLikeCipher( const P6WCHAR* pCipher, p6IEnumKeystore** ppIterator ); enumKeyLikeDescriptiveLabel( const P6WCHAR* pDescription, p6IEnumKeystore** ppIterator ); enumKeyLikeUse( const P6WCHAR* pUse, p6IEnumKeystore** ppIterator ); enumCertBySubject( const P6WCHAR* pSubject, p6IEnumKeystore** ppEnum ); enumLinkBySource( P6UUID* pSource, p6IEnumKeystore** ppEnum ); enumLinkByTarget( P6UUID* pTarget, p6IEnumKeystore** ppEnum ); enumLinkLikeType( const P6WCHAR* pType, p6IEnumKeystore** ppEnum ); enumByAttributeName( P6KEYSTORE_TYPE entryType, const P6WCHAR* pName, p6IEnumKeystore** ppEnum ); enumByAttributeString( P6KEYSTORE_TYPE entryType, const P6WCHAR* pName, const P6WCHAR* pValueStr, p6IEnumKeystore** ppEnum ); enumByAttributeInteger( P6KEYSTORE_TYPE entryType, const P6WCHAR* pName, P6INT32 valueInt, P6INT8 comparitor, p6IEnumKeystore** ppEnum ); enumByAttributeTime( P6KEYSTORE_TYPE entryType, const P6WCHAR* pName, P6TIME timeStamp, P6INT8 comparitor, p6IEnumKeystore** ppEnum );
Here the “comparator” defines the following operations: 0 – find all keys whose length equals the size parameter, 1 – find all keys whose length is greater than the size parameter, and -1 – find all keys whose length is less than the size parameter. The “P6CRYPTOKEYSTATE” type defines key states from the NIST SP 800-130 document (e.g., active, compromised, destroyed). The “P6CRYPTOKEYCLASS: type defines symmetric and asymmetric keys. The “P6CRYPTOCIPHER” type defines the encryption algorithm to be used with the key (e.g., to name a few — AES counter mode, Blowfish ECB, RC2 OFB, Triple DES). The “enumKeyLikeXXX” functions provide SQL like queries (e.g., “.. WHERE cipher like %AES%” ).
To read a key’s meta data we have added the following p6ICryptoKeyGetMeta interface to our p6ICryptoKey component:
getDescriptiveLabel( P6WCHAR* pLabel, P6UINT32 cBuffer, P6UINT32* pWritten ); getUse( P6WCHAR* pUse, P6UINT32 cBuffer, P6UINT32* pWritten ); getState( P6CRYPTOKEYSTATE* pState ); getCipher( P6CRYPTOCIPHER* pCipher ); getStateDate( P6CRYPTOKEYSTATE state, P6TIME* pDate ); getExpiredDate( P6TIME* pExpire );
To write and update a key’s meta data we have also added the following p6ICryptoKeySetMeta interface to our p6ICryptoKey component:
setDescriptiveLabel( const P6WCHAR* pLabel, P6UINT32 cBuffer ); setUse( const P6WCHAR* pUse, P6UINT32 cBuffer ); setState( P6CRYPTOKEYSTATE state ); setCipher( P6CRYPTOCIPHER cipher ); setStateDate( P6CRYPTOKEYSTATE state, P6TIME dateTime ); setExpiredDate( P6TIME expire ); setRenewalDate( P6TIME renewal );
As an example of a typical use of the keystore the following pesudo code demonstrates how an application would update a key’s meta data already stored in a keystore.
// -> list all the active keys p6IKeystore *pKeyStore; p6ICryptoKey *pKey; p6ICryptoKeySetMeta *pKeyMeta; . . . . pKeyStore->enumKeyByState( CKS_ACTIVE, &pIterator ); . . . . // select one of the keys returned by the iterator . . . . // the iterator returns the namespace and name of each key found // -> ask the ketstore to return an instance of the p6ICryptoKey // component (un-serializing the object) pKeyStore->getKey( "a namespace", "a key-name", &pKey ); // -> from the key component obtain the key meta interface pKey->queryInterface( p6ICryptoKeySetMeta, &pKeyMeta ); // -> then change the state of the key due to some condition P6TIME someDateTime; . . . . pKeyMeta->setState( CKS_COMPROMISED ); pKeyMeta->setStateDate( CKS_COMPROMISED, someDateTime ); // -> lastly update the keys meta data stored in the keystore // -> and the keystore will regenerate the signature binding the key // material to its new associated meta data pKeyStore->updateKeyMetaData( "a namespace", "a key-name", pKey );