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:
- 1. Support both client and server applications with a software solution that can be used by itself or included as part of an appliance.
- 2. 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).
- 3. Provide a hierarchical naming structure to help applications organize keystore contents.
- 4. 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.)
- 5. Provide an 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).
- 6. Support the NIST Draft: “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).
How we satisfied the requirements
- 1. P6R’s keystore is part of P6R’s Server Development Platform which provides rich multi-platform client and server functionality. All P6R software is written in C++ but the platform also provides bindings for Perl, PHP, and Python.
- 2. 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 ) ; 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 ) ; deleteKey( const P6WCHAR* pNamespace, const P6WCHAR* pName ); deleteCertificate( const P6WCHAR* pNamespace, const P6WCHAR* pName ); deleteBlob( const P6WCHAR* pNamespace, const P6WCHAR* pName ); updateKeyMetaData( const P6WCHAR* pNamespace, const P6WCHAR* pName, p6ICryptoKey* pKey ); enumEntries( P6KEYSTORE_TYPE byType, const P6WCHAR* pNamespace, p6IEnumKeystore** ppIterator ) ;
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.
- 3. 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.
- 4. 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.
- 5. 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:
However, when using the open() call the keystore wide signature is no longer supported.
- 6. 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 Draft 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.
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 );
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 draft standard defined above (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 );