/* * 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. */ /***************************************************************************** OSToken.cpp The token class; a token is stored in a directory containing several files. Each object is stored in a separate file and a token object is present that has the token specific attributes *****************************************************************************/ #include "config.h" #include "log.h" #include "OSAttributes.h" #include "OSAttribute.h" #include "ObjectFile.h" #include "Directory.h" #include "Generation.h" #include "UUID.h" #include "cryptoki.h" #include "OSToken.h" #include "OSPathSep.h" #include #include #include #include #include #include // Constructor OSToken::OSToken(const std::string inTokenPath) { tokenPath = inTokenPath; tokenDir = new Directory(tokenPath); gen = Generation::create(tokenPath + OS_PATHSEP + "generation", true); tokenObject = new ObjectFile(this, tokenPath + OS_PATHSEP + "token.object", tokenPath + OS_PATHSEP + "token.lock"); tokenMutex = MutexFactory::i()->getMutex(); valid = (gen != NULL) && (tokenMutex != NULL) && tokenDir->isValid() && tokenObject->valid; DEBUG_MSG("Opened token %s", tokenPath.c_str()); index(true); } // Create a new token /*static*/ OSToken* OSToken::createToken(const std::string basePath, const std::string tokenDir, const ByteString& label, const ByteString& serial) { Directory baseDir(basePath); if (!baseDir.isValid()) { ERROR_MSG("Could not create the Directory object"); return NULL; } // Create the token directory if (!baseDir.mkdir(tokenDir)) { // Error msg is generated by mkdir return NULL; } // Create the token object ObjectFile tokenObject(NULL, basePath + OS_PATHSEP + tokenDir + OS_PATHSEP + "token.object", basePath + OS_PATHSEP + tokenDir + OS_PATHSEP + "token.lock", true); if (!tokenObject.valid) { std::string tokenPath = basePath + OS_PATHSEP + tokenDir + OS_PATHSEP + "token.[object|lock]"; ERROR_MSG("Failed to create the token object: %s", tokenPath.c_str()); baseDir.rmdir(tokenDir); return NULL; } // Set the initial attributes CK_ULONG flags = CKF_RNG | CKF_LOGIN_REQUIRED | // FIXME: check CKF_RESTORE_KEY_NOT_NEEDED | CKF_TOKEN_INITIALIZED | CKF_SO_PIN_LOCKED | CKF_SO_PIN_TO_BE_CHANGED; OSAttribute tokenLabel(label); OSAttribute tokenSerial(serial); OSAttribute tokenFlags(flags); if (!tokenObject.setAttribute(CKA_OS_TOKENLABEL, tokenLabel) || !tokenObject.setAttribute(CKA_OS_TOKENSERIAL, tokenSerial) || !tokenObject.setAttribute(CKA_OS_TOKENFLAGS, tokenFlags)) { ERROR_MSG("Failed to set the token attributes"); baseDir.remove(tokenDir + OS_PATHSEP + "token.object"); baseDir.remove(tokenDir + OS_PATHSEP + "token.lock"); baseDir.rmdir(tokenDir); return NULL; } DEBUG_MSG("Created new token %s", tokenDir.c_str()); return new OSToken(basePath + OS_PATHSEP + tokenDir); } // Access an existing token /*static*/ OSToken *OSToken::accessToken(const std::string &basePath, const std::string &tokenDir) { return new OSToken(basePath + OS_PATHSEP + tokenDir); } // Destructor OSToken::~OSToken() { // Clean up std::set cleanUp = allObjects; allObjects.clear(); for (std::set::iterator i = cleanUp.begin(); i != cleanUp.end(); i++) { delete *i; } delete tokenDir; if (gen != NULL) delete gen; MutexFactory::i()->recycleMutex(tokenMutex); delete tokenObject; } // Set the SO PIN bool OSToken::setSOPIN(const ByteString& soPINBlob) { if (!valid) return false; OSAttribute soPIN(soPINBlob); CK_ULONG flags; if (tokenObject->setAttribute(CKA_OS_SOPIN, soPIN) && getTokenFlags(flags)) { flags &= ~CKF_SO_PIN_COUNT_LOW; flags &= ~CKF_SO_PIN_FINAL_TRY; flags &= ~CKF_SO_PIN_LOCKED; flags &= ~CKF_SO_PIN_TO_BE_CHANGED; return setTokenFlags(flags); } return false; } // Get the SO PIN bool OSToken::getSOPIN(ByteString& soPINBlob) { if (!valid || !tokenObject->isValid()) { return false; } if (tokenObject->attributeExists(CKA_OS_SOPIN)) { soPINBlob = tokenObject->getAttribute(CKA_OS_SOPIN).getByteStringValue(); return true; } else { return false; } } // Set the user PIN bool OSToken::setUserPIN(ByteString userPINBlob) { if (!valid) return false; OSAttribute userPIN(userPINBlob); CK_ULONG flags; if (tokenObject->setAttribute(CKA_OS_USERPIN, userPIN) && getTokenFlags(flags)) { flags |= CKF_USER_PIN_INITIALIZED; flags &= ~CKF_USER_PIN_COUNT_LOW; flags &= ~CKF_USER_PIN_FINAL_TRY; flags &= ~CKF_USER_PIN_LOCKED; flags &= ~CKF_USER_PIN_TO_BE_CHANGED; return setTokenFlags(flags); } return false; } // Get the user PIN bool OSToken::getUserPIN(ByteString& userPINBlob) { if (!valid || !tokenObject->isValid()) { return false; } if (tokenObject->attributeExists(CKA_OS_USERPIN)) { userPINBlob = tokenObject->getAttribute(CKA_OS_USERPIN).getByteStringValue(); return true; } else { return false; } } // Retrieve the token label bool OSToken::getTokenLabel(ByteString& label) { if (!valid || !tokenObject->isValid()) { return false; } if (tokenObject->attributeExists(CKA_OS_TOKENLABEL)) { label = tokenObject->getAttribute(CKA_OS_TOKENLABEL).getByteStringValue(); return true; } else { return false; } } // Retrieve the token serial bool OSToken::getTokenSerial(ByteString& serial) { if (!valid || !tokenObject->isValid()) { return false; } if (tokenObject->attributeExists(CKA_OS_TOKENSERIAL)) { serial = tokenObject->getAttribute(CKA_OS_TOKENSERIAL).getByteStringValue(); return true; } else { return false; } } // Get the token flags bool OSToken::getTokenFlags(CK_ULONG& flags) { if (!valid || !tokenObject->isValid()) { return false; } if (tokenObject->attributeExists(CKA_OS_TOKENFLAGS)) { flags = tokenObject->getAttribute(CKA_OS_TOKENFLAGS).getUnsignedLongValue(); // Check if the user PIN is initialised if (tokenObject->attributeExists(CKA_OS_USERPIN)) { flags |= CKF_USER_PIN_INITIALIZED; } return true; } else { return false; } } // Set the token flags bool OSToken::setTokenFlags(const CK_ULONG flags) { if (!valid) return false; OSAttribute tokenFlags(flags); return tokenObject->setAttribute(CKA_OS_TOKENFLAGS, tokenFlags); } // Retrieve objects std::set OSToken::getObjects() { index(); // Make sure that no other thread is in the process of changing // the object list when we return it MutexLocker lock(tokenMutex); return objects; } void OSToken::getObjects(std::set &inObjects) { index(); // Make sure that no other thread is in the process of changing // the object list when we return it MutexLocker lock(tokenMutex); inObjects.insert(objects.begin(),objects.end()); } // Create a new object OSObject* OSToken::createObject() { if (!valid) return NULL; // Generate a name for the object std::string objectUUID = UUID::newUUID(); std::string objectPath = tokenPath + OS_PATHSEP + objectUUID + ".object"; std::string lockPath = tokenPath + OS_PATHSEP + objectUUID + ".lock"; // Create the new object file ObjectFile* newObject = new ObjectFile(this, objectPath, lockPath, true); if (!newObject->valid) { ERROR_MSG("Failed to create new object %s", objectPath.c_str()); delete newObject; return NULL; } // Now add it to the set of objects MutexLocker lock(tokenMutex); objects.insert(newObject); allObjects.insert(newObject); currentFiles.insert(newObject->getFilename()); DEBUG_MSG("(0x%08X) Created new object %s (0x%08X)", this, objectPath.c_str(), newObject); gen->update(); gen->commit(); return newObject; } // Delete an object bool OSToken::deleteObject(OSObject* object) { if (!valid) return false; if (objects.find(object) == objects.end()) { ERROR_MSG("Cannot delete non-existent object 0x%08X", object); return false; } MutexLocker lock(tokenMutex); ObjectFile* fileObject = dynamic_cast(object); if (fileObject == NULL) { ERROR_MSG("Object type not compatible with this token class 0x%08X", object); return false; } // Invalidate the object instance fileObject->invalidate(); // Retrieve the filename of the object std::string objectFilename = fileObject->getFilename(); // Attempt to delete the file if (!tokenDir->remove(objectFilename)) { ERROR_MSG("Failed to delete object file %s", objectFilename.c_str()); return false; } // Retrieve the filename of the lock std::string lockFilename = fileObject->getLockname(); // Attempt to delete the lock if (!tokenDir->remove(lockFilename)) { ERROR_MSG("Failed to delete lock file %s", lockFilename.c_str()); return false; } objects.erase(object); DEBUG_MSG("Deleted object %s", objectFilename.c_str()); gen->update(); gen->commit(); return true; } // Checks if the token is consistent bool OSToken::isValid() { return valid; } // Invalidate the token (for instance if it is deleted) void OSToken::invalidate() { valid = false; } // Delete the token bool OSToken::clearToken() { MutexLocker lock(tokenMutex); // Invalidate the token invalidate(); // First, clear out all objects objects.clear(); // Now, delete all files in the token directory if (!tokenDir->refresh()) { return false; } std::vector tokenFiles = tokenDir->getFiles(); for (std::vector::iterator i = tokenFiles.begin(); i != tokenFiles.end(); i++) { if (!tokenDir->remove(*i)) { ERROR_MSG("Failed to remove %s from token directory %s", i->c_str(), tokenPath.c_str()); return false; } } // Now remove the token directory if (!tokenDir->rmdir("")) { ERROR_MSG("Failed to remove the token directory %s", tokenPath.c_str()); return false; } DEBUG_MSG("Token instance %s was succesfully cleared", tokenPath.c_str()); return true; } // Reset the token bool OSToken::resetToken(const ByteString& label) { CK_ULONG flags; if (!getTokenFlags(flags)) { ERROR_MSG("Failed to get the token attributes"); return false; } // Clean up std::set cleanUp = getObjects(); MutexLocker lock(tokenMutex); for (std::set::iterator i = cleanUp.begin(); i != cleanUp.end(); i++) { ObjectFile* fileObject = dynamic_cast(*i); if (fileObject == NULL) { ERROR_MSG("Object type not compatible with this token class 0x%08X", *i); return false; } // Invalidate the object instance fileObject->invalidate(); // Retrieve the filename of the object std::string objectFilename = fileObject->getFilename(); // Attempt to delete the file if (!tokenDir->remove(objectFilename)) { ERROR_MSG("Failed to delete object file %s", objectFilename.c_str()); return false; } // Retrieve the filename of the lock std::string lockFilename = fileObject->getLockname(); // Attempt to delete the lock if (!tokenDir->remove(lockFilename)) { ERROR_MSG("Failed to delete lock file %s", lockFilename.c_str()); return false; } objects.erase(*i); DEBUG_MSG("Deleted object %s", objectFilename.c_str()); } // The user PIN has been removed flags &= ~CKF_USER_PIN_INITIALIZED; flags &= ~CKF_USER_PIN_COUNT_LOW; flags &= ~CKF_USER_PIN_FINAL_TRY; flags &= ~CKF_USER_PIN_LOCKED; flags &= ~CKF_USER_PIN_TO_BE_CHANGED; // Set new token attributes OSAttribute tokenLabel(label); OSAttribute tokenFlags(flags); if (!tokenObject->setAttribute(CKA_OS_TOKENLABEL, tokenLabel) || !tokenObject->setAttribute(CKA_OS_TOKENFLAGS, tokenFlags)) { ERROR_MSG("Failed to set the token attributes"); return false; } if (tokenObject->attributeExists(CKA_OS_USERPIN) && !tokenObject->deleteAttribute(CKA_OS_USERPIN)) { ERROR_MSG("Failed to remove USERPIN"); return false; } DEBUG_MSG("Token instance %s was succesfully reset", tokenPath.c_str()); gen->update(); gen->commit(); return true; } // Index the token bool OSToken::index(bool isFirstTime /* = false */) { // No access to object mutable fields before MutexLocker lock(tokenMutex); // Check if re-indexing is required if (!isFirstTime && (!valid || !gen->wasUpdated())) { DEBUG_MSG("No re-indexing is required"); return true; } // Check the integrity if (!tokenDir->refresh() || !tokenObject->valid) { ERROR_MSG("Token integrity check failed"); valid = false; return false; } DEBUG_MSG("Token %s has changed", tokenPath.c_str()); // Retrieve the directory listing std::vector tokenFiles = tokenDir->getFiles(); // Filter out the objects std::set newSet; for (std::vector::iterator i = tokenFiles.begin(); i != tokenFiles.end(); i++) { if ((i->size() > 7) && (!(i->substr(i->size() - 7).compare(".object"))) && (i->compare("token.object"))) { newSet.insert(*i); } else { DEBUG_MSG("Ignored file %s", i->c_str()); } } // Compute the changes compared to the last list of files std::set addedFiles; std::set removedFiles; if (!isFirstTime) { // First compute which files were added for (std::set::iterator i = newSet.begin(); i != newSet.end(); i++) { if (currentFiles.find(*i) == currentFiles.end()) { addedFiles.insert(*i); } } // Now compute which files were removed for (std::set::iterator i = currentFiles.begin(); i != currentFiles.end(); i++) { if (newSet.find(*i) == newSet.end()) { removedFiles.insert(*i); } } } else { addedFiles = newSet; } currentFiles = newSet; DEBUG_MSG("%d objects were added and %d objects were removed", addedFiles.size(), removedFiles.size()); DEBUG_MSG("Current directory set contains %d objects", currentFiles.size()); // Now update the set of objects // Add new objects for (std::set::iterator i = addedFiles.begin(); i != addedFiles.end(); i++) { if ((i->find_last_of('.') == std::string::npos) || (i->substr(i->find_last_of('.')) != ".object")) { continue; } std::string lockName(*i); lockName.replace(lockName.find_last_of('.'), std::string::npos, ".lock"); // Create a new token object for the added file ObjectFile* newObject = new ObjectFile(this, tokenPath + OS_PATHSEP + *i, tokenPath + OS_PATHSEP + lockName); // Add the object, even invalid ones. // This is so the we can read the attributes once // the other process has finished writing to disc. DEBUG_MSG("(0x%08X) New object %s (0x%08X) added", this, newObject->getFilename().c_str(), newObject); objects.insert(newObject); allObjects.insert(newObject); } // Remove deleted objects std::set newObjects; for (std::set::iterator i = objects.begin(); i != objects.end(); i++) { ObjectFile* fileObject = dynamic_cast((*i)); if (fileObject == NULL) { ERROR_MSG("Object type not compatible with this token class 0x%08X", (*i)); return false; } DEBUG_MSG("Processing %s (0x%08X)", fileObject->getFilename().c_str(), *i); if (removedFiles.find(fileObject->getFilename()) == removedFiles.end()) { DEBUG_MSG("Adding object %s", fileObject->getFilename().c_str()); // This object gets to stay in the set newObjects.insert(*i); } else { fileObject->invalidate(); } } // Set the new objects objects = newObjects; DEBUG_MSG("The token now contains %d objects", objects.size()); return true; }