| /* |
| * Copyright (c) 2010 SURFnet bv |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /***************************************************************************** |
| SecureDataManager.cpp |
| |
| The secure data manager main class. Every token instance has a secure data |
| manager instance member that is used to decrypt and encrypt sensitive object |
| attributes such as key material. The secure data manager maintains a key blob |
| containing a 256-bit AES key that is used in this decryption and encryption |
| process. The key blob itself is encrypted using a PBE derived key that is |
| derived from the user PIN and a PBE key that is derived from the SO PIN. It |
| is up to the token to enforce access control based on which user is logged |
| in; authentication using the SO PIN is required to be able to change the |
| user PIN. The master key that is used to decrypt/encrypt sensitive attributes |
| is stored in memory under a mask that is changed every time the key is used. |
| *****************************************************************************/ |
| |
| #include "config.h" |
| #include "SecureDataManager.h" |
| #include "CryptoFactory.h" |
| #include "AESKey.h" |
| #include "SymmetricAlgorithm.h" |
| #include "RFC4880.h" |
| |
| // Constructors |
| |
| // Initialise the object; called by all constructors |
| void SecureDataManager::initObject() |
| { |
| // Get an RNG instance |
| rng = CryptoFactory::i()->getRNG(); |
| |
| // Get an AES implementation |
| aes = CryptoFactory::i()->getSymmetricAlgorithm(SymAlgo::AES); |
| |
| // Initialise masking data |
| mask = new ByteString(); |
| |
| rng->generateRandom(*mask, 32); |
| |
| // Set the initial login state |
| soLoggedIn = userLoggedIn = false; |
| |
| // Set the magic |
| magic = ByteString("524A52"); // RJR |
| |
| // Get a mutex |
| dataMgrMutex = MutexFactory::i()->getMutex(); |
| } |
| |
| // Constructs a new SecureDataManager for a blank token; actual |
| // initialisation is done by setting the SO PIN |
| SecureDataManager::SecureDataManager() |
| { |
| initObject(); |
| } |
| |
| // Constructs a SecureDataManager using the specified key blob |
| SecureDataManager::SecureDataManager(const ByteString& soPINBlob, const ByteString& userPINBlob) |
| { |
| initObject(); |
| |
| // De-serialise the key blob |
| soEncryptedKey = soPINBlob; |
| userEncryptedKey = userPINBlob; |
| } |
| |
| // Destructor |
| SecureDataManager::~SecureDataManager() |
| { |
| // Recycle the AES instance |
| CryptoFactory::i()->recycleSymmetricAlgorithm(aes); |
| |
| // Clean up the mask |
| delete mask; |
| |
| MutexFactory::i()->recycleMutex(dataMgrMutex); |
| } |
| |
| // Generic function for creating an encrypted version of the key from the specified passphrase |
| bool SecureDataManager::pbeEncryptKey(const ByteString& passphrase, ByteString& encryptedKey) |
| { |
| // Generate salt |
| ByteString salt; |
| |
| if (!rng->generateRandom(salt, 8)) return false; |
| |
| // Derive the key using RFC4880 PBE |
| AESKey* pbeKey = NULL; |
| |
| if (!RFC4880::PBEDeriveKey(passphrase, salt, &pbeKey)) |
| { |
| return false; |
| } |
| |
| // Add the salt |
| encryptedKey.wipe(); |
| encryptedKey += salt; |
| |
| // Generate random IV |
| ByteString IV; |
| |
| if (!rng->generateRandom(IV, aes->getBlockSize())) return false; |
| |
| // Add the IV |
| encryptedKey += IV; |
| |
| // Encrypt the data |
| ByteString block; |
| |
| if (!aes->encryptInit(pbeKey, SymMode::CBC, IV)) |
| { |
| delete pbeKey; |
| |
| return false; |
| } |
| |
| // First, add the magic |
| if (!aes->encryptUpdate(magic, block)) |
| { |
| delete pbeKey; |
| |
| return false; |
| } |
| |
| encryptedKey += block; |
| |
| // Then, add the key itself |
| ByteString key; |
| |
| { |
| MutexLocker lock(dataMgrMutex); |
| |
| unmask(key); |
| |
| bool rv = aes->encryptUpdate(key, block); |
| |
| remask(key); |
| |
| if (!rv) |
| { |
| delete pbeKey; |
| |
| return false; |
| } |
| } |
| |
| encryptedKey += block; |
| |
| // And finalise encryption |
| if (!aes->encryptFinal(block)) |
| { |
| delete pbeKey; |
| |
| return false; |
| } |
| |
| encryptedKey += block; |
| |
| delete pbeKey; |
| |
| return true; |
| } |
| |
| // Set the SO PIN (requires either a blank SecureDataManager or the |
| // SO to have logged in previously) |
| bool SecureDataManager::setSOPIN(const ByteString& soPIN) |
| { |
| // Check the new PIN |
| if (soPIN.size() == 0) |
| { |
| DEBUG_MSG("Zero length PIN specified"); |
| |
| return false; |
| } |
| |
| // Check if the SO needs to be logged in |
| if ((soEncryptedKey.size() > 0) && !soLoggedIn) |
| { |
| DEBUG_MSG("SO must be logged in to change the SO PIN"); |
| |
| return false; |
| } |
| |
| // If no SO PIN was set, then this is a SecureDataManager for a blank token. This |
| // means a new key has to be generated |
| if (soEncryptedKey.size() == 0) |
| { |
| ByteString key; |
| |
| rng->generateRandom(key, 32); |
| |
| remask(key); |
| } |
| |
| return pbeEncryptKey(soPIN, soEncryptedKey); |
| } |
| |
| // Set the user PIN (requires either the SO or the user to have logged |
| // in previously) |
| bool SecureDataManager::setUserPIN(const ByteString& userPIN) |
| { |
| // Check if the SO or the user is logged in |
| if (!soLoggedIn && !userLoggedIn) |
| { |
| DEBUG_MSG("Must be logged in to change the user PIN"); |
| |
| return false; |
| } |
| |
| // Check the new PIN |
| if (userPIN.size() == 0) |
| { |
| DEBUG_MSG("Zero length PIN specified"); |
| |
| return false; |
| } |
| |
| return pbeEncryptKey(userPIN, userEncryptedKey); |
| } |
| |
| // Generic login function |
| bool SecureDataManager::login(const ByteString& passphrase, const ByteString& encryptedKey) |
| { |
| // Log out first |
| this->logout(); |
| |
| // First, take the salt from the encrypted key |
| ByteString salt = encryptedKey.substr(0,8); |
| |
| // Then, take the IV from the encrypted key |
| ByteString IV = encryptedKey.substr(8, aes->getBlockSize()); |
| |
| // Now, take the encrypted data from the encrypted key |
| ByteString encryptedKeyData = encryptedKey.substr(8 + aes->getBlockSize()); |
| |
| // Derive the PBE key |
| AESKey* pbeKey = NULL; |
| |
| if (!RFC4880::PBEDeriveKey(passphrase, salt, &pbeKey)) |
| { |
| return false; |
| } |
| |
| // Decrypt the key data |
| ByteString decryptedKeyData; |
| ByteString finalBlock; |
| |
| // NOTE: The login will fail here if incorrect passphrase is supplied |
| if (!aes->decryptInit(pbeKey, SymMode::CBC, IV) || |
| !aes->decryptUpdate(encryptedKeyData, decryptedKeyData) || |
| !aes->decryptFinal(finalBlock)) |
| { |
| delete pbeKey; |
| |
| return false; |
| } |
| |
| delete pbeKey; |
| |
| decryptedKeyData += finalBlock; |
| |
| // Check the magic |
| if (decryptedKeyData.substr(0, 3) != magic) |
| { |
| // The passphrase was incorrect |
| DEBUG_MSG("Incorrect passphrase supplied"); |
| |
| return false; |
| } |
| |
| // Strip off the magic |
| ByteString key = decryptedKeyData.substr(3); |
| |
| // And mask the key |
| decryptedKeyData.wipe(); |
| |
| MutexLocker lock(dataMgrMutex); |
| remask(key); |
| |
| return true; |
| } |
| |
| // Log in using the SO PIN |
| bool SecureDataManager::loginSO(const ByteString& soPIN) |
| { |
| return (soLoggedIn = login(soPIN, soEncryptedKey)); |
| } |
| |
| // Log in using the user PIN |
| bool SecureDataManager::loginUser(const ByteString& userPIN) |
| { |
| return (userLoggedIn = login(userPIN, userEncryptedKey)); |
| } |
| |
| // Generic re-authentication function |
| bool SecureDataManager::reAuthenticate(const ByteString& passphrase, const ByteString& encryptedKey) |
| { |
| // First, take the salt from the encrypted key |
| ByteString salt = encryptedKey.substr(0,8); |
| |
| // Then, take the IV from the encrypted key |
| ByteString IV = encryptedKey.substr(8, aes->getBlockSize()); |
| |
| // Now, take the encrypted data from the encrypted key |
| ByteString encryptedKeyData = encryptedKey.substr(8 + aes->getBlockSize()); |
| |
| // Derive the PBE key |
| AESKey* pbeKey = NULL; |
| |
| if (!RFC4880::PBEDeriveKey(passphrase, salt, &pbeKey)) |
| { |
| return false; |
| } |
| |
| // Decrypt the key data |
| ByteString decryptedKeyData; |
| ByteString finalBlock; |
| |
| // NOTE: The login will fail here if incorrect passphrase is supplied |
| if (!aes->decryptInit(pbeKey, SymMode::CBC, IV) || |
| !aes->decryptUpdate(encryptedKeyData, decryptedKeyData) || |
| !aes->decryptFinal(finalBlock)) |
| { |
| delete pbeKey; |
| |
| return false; |
| } |
| |
| delete pbeKey; |
| |
| decryptedKeyData += finalBlock; |
| |
| // Check the magic |
| if (decryptedKeyData.substr(0, 3) != magic) |
| { |
| // The passphrase was incorrect |
| DEBUG_MSG("Incorrect passphrase supplied"); |
| |
| return false; |
| } |
| |
| // And mask the key |
| decryptedKeyData.wipe(); |
| |
| return true; |
| } |
| |
| // Re-authenticate the SO |
| bool SecureDataManager::reAuthenticateSO(const ByteString& soPIN) |
| { |
| return reAuthenticate(soPIN, soEncryptedKey); |
| } |
| |
| // Re-authenticate the user |
| bool SecureDataManager::reAuthenticateUser(const ByteString& userPIN) |
| { |
| return reAuthenticate(userPIN, userEncryptedKey); |
| } |
| |
| // Log out |
| void SecureDataManager::logout() |
| { |
| MutexLocker lock(dataMgrMutex); |
| |
| // Clear the logged in state |
| soLoggedIn = userLoggedIn = false; |
| |
| // Clear the masked key |
| maskedKey.wipe(); |
| } |
| |
| // Decrypt the supplied data |
| bool SecureDataManager::decrypt(const ByteString& encrypted, ByteString& plaintext) |
| { |
| // Check the object logged in state |
| if ((!userLoggedIn && !soLoggedIn) || (maskedKey.size() != 32)) |
| { |
| return false; |
| } |
| |
| // Do not attempt decryption of empty byte strings |
| if (encrypted.size() == 0) |
| { |
| plaintext = ByteString(""); |
| return true; |
| } |
| |
| AESKey theKey(256); |
| ByteString unmaskedKey; |
| |
| { |
| MutexLocker lock(dataMgrMutex); |
| |
| unmask(unmaskedKey); |
| |
| theKey.setKeyBits(unmaskedKey); |
| |
| remask(unmaskedKey); |
| } |
| |
| // Take the IV from the input data |
| ByteString IV = encrypted.substr(0, aes->getBlockSize()); |
| |
| if (IV.size() != aes->getBlockSize()) |
| { |
| ERROR_MSG("Invalid IV in encrypted data"); |
| |
| return false; |
| } |
| |
| ByteString finalBlock; |
| |
| if (!aes->decryptInit(&theKey, SymMode::CBC, IV) || |
| !aes->decryptUpdate(encrypted.substr(aes->getBlockSize()), plaintext) || |
| !aes->decryptFinal(finalBlock)) |
| { |
| return false; |
| } |
| |
| plaintext += finalBlock; |
| |
| return true; |
| } |
| |
| // Encrypt the supplied data |
| bool SecureDataManager::encrypt(const ByteString& plaintext, ByteString& encrypted) |
| { |
| // Check the object logged in state |
| if ((!userLoggedIn && !soLoggedIn) || (maskedKey.size() != 32)) |
| { |
| return false; |
| } |
| |
| AESKey theKey(256); |
| ByteString unmaskedKey; |
| |
| { |
| MutexLocker lock(dataMgrMutex); |
| |
| unmask(unmaskedKey); |
| |
| theKey.setKeyBits(unmaskedKey); |
| |
| remask(unmaskedKey); |
| } |
| |
| // Wipe encrypted data block |
| encrypted.wipe(); |
| |
| // Generate random IV |
| ByteString IV; |
| |
| if (!rng->generateRandom(IV, aes->getBlockSize())) return false; |
| |
| ByteString finalBlock; |
| |
| if (!aes->encryptInit(&theKey, SymMode::CBC, IV) || |
| !aes->encryptUpdate(plaintext, encrypted) || |
| !aes->encryptFinal(finalBlock)) |
| { |
| return false; |
| } |
| |
| encrypted += finalBlock; |
| |
| // Add IV to output data |
| encrypted = IV + encrypted; |
| |
| return true; |
| } |
| |
| // Returns the key blob for the SO PIN |
| ByteString SecureDataManager::getSOPINBlob() |
| { |
| return soEncryptedKey; |
| } |
| |
| // Returns the key blob for the user PIN |
| ByteString SecureDataManager::getUserPINBlob() |
| { |
| return userEncryptedKey; |
| } |
| |
| // Unmask the key |
| void SecureDataManager::unmask(ByteString& key) |
| { |
| key = maskedKey; |
| key ^= *mask; |
| } |
| |
| // Remask the key |
| void SecureDataManager::remask(ByteString& key) |
| { |
| // Generate a new mask |
| rng->generateRandom(*mask, 32); |
| |
| key ^= *mask; |
| maskedKey = key; |
| } |
| |
| // Check if the SO is logged in |
| bool SecureDataManager::isSOLoggedIn() |
| { |
| return soLoggedIn; |
| } |
| |
| // Check if the user is logged in |
| bool SecureDataManager::isUserLoggedIn() |
| { |
| return userLoggedIn; |
| } |
| |