blob: 51b17d0c64fffc923282ff12779368ddd7bfd8e2 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Contains the implementation of class Pkcs11Init.
#include "pkcs11_init.h"
#include <iostream>
#include <base/logging.h>
#include <base/string_util.h>
#include <glib.h>
#include <opencryptoki/pkcs11.h>
#include "platform.h"
namespace cryptohome {
// static
const CK_SLOT_ID Pkcs11Init::kDefaultTpmSlotId = 0;
const CK_ULONG Pkcs11Init::kMaxPinLen = 9;
const CK_ULONG Pkcs11Init::kMaxLabelLen = 32;
// openCryptoki 2.2.4 PIN defaults as per the README at:
// http://opencryptoki.git.sourceforge.net/git/gitweb.cgi?p=opencryptoki
// /opencryptoki;a=summary
const CK_CHAR Pkcs11Init::kDefaultOpencryptokiSoPin[] = "87654321";
const CK_CHAR Pkcs11Init::kDefaultOpencryptokiUserPin[] = "12345678";
const CK_CHAR Pkcs11Init::kDefaultSoPin[] = "000000";
const CK_CHAR Pkcs11Init::kDefaultUserPin[] = "111111";
const CK_CHAR Pkcs11Init::kDefaultLabel[] = "User-Specific TPM Token";
const char Pkcs11Init::kOpencryptokiDir[] = "/var/lib/opencryptoki";
const char Pkcs11Init::kUserTokenLink[] = "/var/lib/opencryptoki/tpm/chronos";
const char Pkcs11Init::kRootTokenLink[] = "/var/lib/opencryptoki/tpm/root";
const char Pkcs11Init::kUserTokenDir[] = "/home/chronos/user/.tpm";
const char Pkcs11Init::kRootTokenDir[] = "./chronos";
const char Pkcs11Init::kPkcs11Group[] = "pkcs11";
const char Pkcs11Init::kOldTokenEntry[] =
"/var/lib/opencryptoki/pk_config_data";
const std::string Pkcs11Init::kPkcs11StartupPath = "/usr/sbin/pkcs11_startup";
const std::string Pkcs11Init::kPkcsSlotdPath = "/usr/sbin/pkcsslotd";
const std::string Pkcs11Init::kPkcsSlotPath = "/usr/sbin/pkcs_slot";
const std::string Pkcs11Init::kPkcsSlotCmd[] = { Pkcs11Init::kPkcsSlotPath,
"0", "tpm" };
// This file in the user's cryptohome signifies that Pkcs11 initialization
// was completed for the user.
const std::string Pkcs11Init::kPkcs11InitializedFile =
"/home/chronos/user/.tpm/.isinitialized";
extern const char* kTpmOwnedFile;
extern const std::string kDefaultSharedUser;
// Helper function to copy a CK_CHAR string |src| to another CK_CHAR string
// |dest| with at most most |len-1| characters. |dest| is then null-terminated.
void CKCharcpy(CK_CHAR* dest, const CK_CHAR* src, CK_ULONG len) {
strncpy(reinterpret_cast<char*>(dest),
reinterpret_cast<const char*>(src),
len - 1);
dest[len - 1] = '\0';
}
void Pkcs11Init::GetTpmTokenInfo(gchar **OUT_label,
gchar **OUT_user_pin) {
*OUT_label = g_strdup(reinterpret_cast<const gchar *>(kDefaultLabel));
*OUT_user_pin = g_strdup(reinterpret_cast<const gchar *>(kDefaultUserPin));
}
bool Pkcs11Init::Initialize() {
Platform platform;
// Determine required uid and gids.
if (!platform.GetGroupId(kPkcs11Group, &pkcs11_group_id_)) {
LOG(ERROR) << "Couldn't get the group ID for group " << kPkcs11Group;
return false;
}
if (!platform.GetUserId(kDefaultSharedUser, &chronos_user_id_,
&chronos_group_id_)) {
LOG(ERROR) << "Couldn't get the user/group ID for user "
<< kDefaultSharedUser;
return false;
}
if (!SetUpPkcs11System())
return false;
if (IsTokenBroken()) {
LOG(INFO) << "Broken or uninitialized token. Removing user token dir "
<< kUserTokenDir << " just to be safe.";
RemoveUserTokenDir();
if (!SetUpLinksAndPermissions())
return false;
return SetUpTPMToken();
}
LOG(INFO) << "PKCS#11 is already initialized.";
return true;
}
bool Pkcs11Init::SetUpPkcs11System() {
Platform platform;
// Run the pkcs11_startup script that will detect available tokens and update
// pk_config_data appropriately. This is used later by pkcsslotd.
std::string startup_cmd = kPkcs11StartupPath;
std::string startup_args[1] = { startup_cmd };
std::vector<std::string> startup_arguments;
startup_arguments.assign(startup_args,
startup_args + arraysize(startup_args));
// TODO(gauravsh): Consider moving to using process functions from
// src/common/chromeos/process.h instead.
//
// Note: This must be run as root so that it can create the necessary
// directories with the correct permissions under /var/lib. Since it's a
// and not a service daemon, we are OK with that.
if (!platform.Exec(startup_cmd, startup_arguments,
static_cast<uid_t>(0), // Run as root.
pkcs11_group_id_))
return false;
// Start the slot manager daemon (pkcsslotd).
std::string slotd_cmd = kPkcsSlotdPath;
std::string slotd_args[1] = { slotd_cmd };
std::vector<std::string> slotd_arguments;
slotd_arguments.assign(slotd_args, slotd_args + arraysize(slotd_args));
// TODO(gauravsh): Figure out if pkcsslotd can be run as a different primary
// uid.
if (!platform.Exec(slotd_cmd, slotd_arguments, chronos_user_id_,
pkcs11_group_id_))
return false;
return true;
}
bool Pkcs11Init::SetUpTPMToken() {
Platform platform;
// Setup the TPM as a token.
std::vector<std::string> arguments;
arguments.assign(kPkcsSlotCmd, kPkcsSlotCmd + arraysize(kPkcsSlotCmd));
if (!platform.Exec(kPkcsSlotPath, arguments, chronos_user_id_,
pkcs11_group_id_)) {
LOG(ERROR) << "Couldn't configure the tpm as a token. "
<< kPkcsSlotPath << " " << kPkcsSlotCmd[1] << " "
<< kPkcsSlotCmd[2] << "failed.";
return false;
}
CK_RV rv = C_Initialize(NULL);
if (rv != CKR_CRYPTOKI_ALREADY_INITIALIZED && rv != CKR_OK) {
LOG(ERROR) << "C_Initialize returned error code 0x" << std::hex << rv;
return false;
}
CK_SLOT_ID slot_id = kDefaultTpmSlotId;
// Initialize the token by login using the default openCryptoki SO PIN.
CK_CHAR old_pin[kMaxPinLen];
CK_CHAR ck_label[kMaxLabelLen];
CKCharcpy(old_pin, kDefaultOpencryptokiSoPin, sizeof(old_pin));
CKCharcpy(ck_label, kDefaultLabel, sizeof(ck_label));
LOG(INFO) << "Initializing token by using default openCryptoki SO PIN.";
rv = C_InitToken(slot_id,
old_pin, strlen(reinterpret_cast<const char*>(old_pin)),
ck_label);
if (rv != CKR_OK) {
LOG(ERROR) << "C_InitToken returned error code 0x" << std::hex << rv;
return false;
}
// Reset the SO PIN to our default value.
CK_CHAR new_pin[kMaxPinLen];
CKCharcpy(new_pin, kDefaultSoPin, sizeof(new_pin));
LOG(INFO) << "Resetting the SO PIN on the token to our default value.";
if (!SetPin(slot_id, CKU_SO,
old_pin, strlen(reinterpret_cast<const char*>(old_pin)),
new_pin, strlen(reinterpret_cast<const char*>(new_pin)))) {
LOG(ERROR) << "Couldn't reset the SO PIN on the token.";
return false;
}
// Reset the User PIN to our default value.
CKCharcpy(old_pin, kDefaultOpencryptokiUserPin, sizeof(old_pin));
CKCharcpy(new_pin, kDefaultUserPin, sizeof(new_pin));
LOG(INFO) << "Resetting the User PIN on the token to our default value.";
if (!SetPin(slot_id, CKU_USER,
old_pin, strlen(reinterpret_cast<const char*>(old_pin)),
new_pin, strlen(reinterpret_cast<const char*>(new_pin)))) {
LOG(ERROR) << "Couldn't reset the User PIN on the token.";
return false;
}
LOG(INFO) << "Token initialization complete.";
return SetInitialized(true);
}
bool Pkcs11Init::SetUpLinksAndPermissions() {
Platform platform;
std::string opencryptoki_tpm = StringPrintf("%s/tpm", kOpencryptokiDir);
if (!platform.CreateDirectory(opencryptoki_tpm)) {
LOG(ERROR) << "Couldn't create openCryptoki token directory "
<< opencryptoki_tpm;
return false;
}
// Ensure symbolic links for the token directories are correctly setup.
// TODO(gauravsh): (crosbug.com/7284) Patch openCryptoki so that this is no
// longer necessary.
if (!platform.Symlink(std::string(kUserTokenDir),
std::string(kUserTokenLink)))
return false;
if (!platform.Symlink(std::string(kRootTokenDir),
std::string(kRootTokenLink)))
return false;
// Remove old token directory.
if (platform.DirectoryExists(kOldTokenEntry) &&
!platform.DeleteFile(kOldTokenEntry, true))
return false;
// Create token directory, since initialization neither creates or populates
// it.
std::string token_obj_dir = StringPrintf("%s/TOK_OBJ", kUserTokenDir);
if (!platform.DirectoryExists(token_obj_dir) &&
!platform.CreateDirectory(token_obj_dir))
return false;
// Set correct permissions on opencryptoki directory (root:$PKCS11_GROUP_ID).
if (!platform.SetOwnershipRecursive(std::string(kOpencryptokiDir),
static_cast<uid_t>(0), // Root uid is 0.
pkcs11_group_id_)) {
LOG(ERROR) << "Couldn't set file ownership for " << kOpencryptokiDir;
return false;
}
// Setup permissions on the user token directory to ensure that users can
// access their own data.
if (!platform.SetOwnershipRecursive(std::string(kUserTokenDir),
chronos_user_id_,
pkcs11_group_id_)) {
LOG(ERROR) << "Couldn't set file ownership for " << kOpencryptokiDir;
return false;
}
return true;
}
bool Pkcs11Init::IsTokenBroken() {
Platform platform;
// A non-existent token is also broken.
if (!platform.FileExists(StringPrintf("%s/NVTOK.DAT", kUserTokenDir)))
return true;
if (!platform.FileExists(kTpmOwnedFile)) {
LOG(WARNING) << "TPM is not owned, token can't valid.";
return true;
}
if (!platform.FileExists(StringPrintf("%s/PRIVATE_ROOT_KEY.pem",
kUserTokenDir)) ||
!platform.FileExists(StringPrintf("%s/TOK_OBJ/70000000",
kUserTokenDir))) {
LOG(WARNING) << "PKCS#11 token is missing some files. Possibly not yet "
<< "initialized? TOK_OBJ contents were: ";
// Log the contents of what was observed in exists in the TOK_OBJ directory.
std::vector<std::string> tok_file_list;
platform.EnumerateFiles(StringPrintf("%s/TOK_OBJ/", kUserTokenDir), true,
&tok_file_list);
for (std::vector<std::string>::iterator it = tok_file_list.begin();
it != tok_file_list.end(); ++it)
LOG(WARNING) << *it;
return true;
}
// At this point, we are done with basic sanity checking.
// Now check if our "it is initialized" special file is still there.
if (!platform.FileExists(kPkcs11InitializedFile))
return true;
LOG(INFO) << "PKCS#11 token looks ok.";
return false;
}
bool Pkcs11Init::RemoveUserTokenDir() {
Platform platform;
if (platform.DirectoryExists(kUserTokenDir)) {
return platform.DeleteFile(kUserTokenDir, true);
}
return true;
}
bool Pkcs11Init::SetInitialized(bool status) {
Platform platform;
if (!status) {
// Remove initialization state.
platform.DeleteFile(kPkcs11InitializedFile, false);
is_initialized_ = false;
return true;
}
chromeos::Blob empty_blob;
if (!platform.WriteFile(kPkcs11InitializedFile, empty_blob))
return false;
is_initialized_ = true;
return true;
}
bool Pkcs11Init::SetPin(CK_SLOT_ID slot_id, CK_USER_TYPE user,
CK_CHAR_PTR old_pin, CK_ULONG old_pin_len,
CK_CHAR_PTR new_pin, CK_ULONG new_pin_len) {
CK_RV rv;
CK_SESSION_HANDLE session_handle = 0;
rv = C_OpenSession(slot_id, CKF_RW_SESSION | CKF_SERIAL_SESSION,
NULL, NULL,
&session_handle);
if (rv != CKR_OK) {
LOG(ERROR) << "C_OpenSession returned error code 0x" << std::hex << rv;
return false;
}
rv = C_Login(session_handle, user, old_pin, old_pin_len);
if (rv != CKR_OK) {
LOG(ERROR) << "Couldn't login using the old pin.";
LOG(ERROR) << "C_Login returned error code 0x" << std::hex << rv;
return false;
}
rv = C_SetPIN(session_handle, old_pin, old_pin_len, new_pin, new_pin_len);
if (rv != CKR_OK) {
C_CloseAllSessions(slot_id);
LOG(ERROR) << "C_SetPIN returned error code 0x" << std::hex << rv;
return false;
}
rv = C_CloseAllSessions(slot_id);
if (rv != CKR_OK) {
LOG(ERROR) << "C_CloseAllSessions returned error code 0x" << std::hex << rv;
return false;
}
return true;
}
} // namespace cryptohome