blob: 272e1fd42cf172f20763381d828ce1aa08b70f9b [file] [log] [blame]
// Copyright (c) 2013 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.
#include "cryptohome/pkcs11_keystore.h"
#include <string>
#include <base/bind.h>
#include <base/callback.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <chaps/pkcs11/cryptoki.h>
#include <brillo/cryptohome.h>
#include <brillo/secure_blob.h>
#include <crypto/scoped_openssl_types.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "cryptohome/cryptolib.h"
#include "cryptohome/pkcs11_init.h"
using base::FilePath;
using brillo::SecureBlob;
namespace cryptohome {
typedef crypto::ScopedOpenSSL<X509, X509_free> ScopedX509;
// An arbitrary application ID to identify PKCS #11 objects.
const char kApplicationID[] = "CrOS_d5bbc079d2497110feadfc97c40d718ae46f4658";
// A helper class to scope a PKCS #11 session.
class ScopedSession {
public:
explicit ScopedSession(CK_SLOT_ID slot)
: handle_(CK_INVALID_HANDLE) {
CK_RV rv = C_Initialize(NULL);
if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) {
// This may be normal in a test environment.
LOG(INFO) << "PKCS #11 is not available.";
return;
}
CK_FLAGS flags = CKF_RW_SESSION | CKF_SERIAL_SESSION;
if (C_OpenSession(slot, flags, NULL, NULL, &handle_) != CKR_OK) {
LOG(ERROR) << "Failed to open PKCS #11 session.";
return;
}
}
~ScopedSession() {
if (IsValid() && (C_CloseSession(handle_) != CKR_OK)) {
LOG(WARNING) << "Failed to close PKCS #11 session.";
handle_ = CK_INVALID_HANDLE;
}
}
CK_SESSION_HANDLE handle() {
return handle_;
}
bool IsValid() {
return (handle_ != CK_INVALID_HANDLE);
}
private:
CK_SESSION_HANDLE handle_;
DISALLOW_COPY_AND_ASSIGN(ScopedSession);
};
Pkcs11KeyStore::Pkcs11KeyStore() : default_pkcs11_init_(new Pkcs11Init),
pkcs11_init_(default_pkcs11_init_.get()) {}
Pkcs11KeyStore::Pkcs11KeyStore(Pkcs11Init* pkcs11_init)
: pkcs11_init_(pkcs11_init) {}
Pkcs11KeyStore::~Pkcs11KeyStore() {}
bool Pkcs11KeyStore::Read(bool is_user_specific,
const std::string& username,
const std::string& key_name,
SecureBlob* key_data) {
CK_SLOT_ID slot;
if (!GetUserSlot(is_user_specific, username, &slot))
return false;
ScopedSession session(slot);
if (!session.IsValid())
return false;
CK_OBJECT_HANDLE key_handle = FindObject(session.handle(), key_name);
if (key_handle == CK_INVALID_HANDLE)
return false;
// First get the attribute with a NULL buffer which will give us the length.
CK_ATTRIBUTE attribute = {CKA_VALUE, NULL, 0};
if (C_GetAttributeValue(session.handle(),
key_handle,
&attribute, 1) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to read key data: " << key_name;
return false;
}
SecureBlob value_buffer(attribute.ulValueLen);
attribute.pValue = value_buffer.data();
if (C_GetAttributeValue(session.handle(),
key_handle,
&attribute, 1) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to read key data: " << key_name;
return false;
}
key_data->swap(value_buffer);
return true;
}
bool Pkcs11KeyStore::Write(bool is_user_specific,
const std::string& username,
const std::string& key_name,
const SecureBlob& key_data) {
// Delete any existing key with the same name.
if (!Delete(is_user_specific, username, key_name))
return false;
CK_SLOT_ID slot;
if (!GetUserSlot(is_user_specific, username, &slot))
return false;
ScopedSession session(slot);
if (!session.IsValid())
return false;
// Create a new data object for the key.
CK_OBJECT_CLASS object_class = CKO_DATA;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_ATTRIBUTE attributes[] = {
{CKA_CLASS, &object_class, sizeof(object_class)},
{
CKA_LABEL,
const_cast<char*>(key_name.data()),
key_name.size()
},
{
CKA_VALUE,
const_cast<uint8_t*>(key_data.data()),
key_data.size()
},
{
CKA_APPLICATION,
const_cast<char*>(kApplicationID),
arraysize(kApplicationID)
},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &true_value, sizeof(true_value)},
{CKA_MODIFIABLE, &false_value, sizeof(false_value)}
};
CK_OBJECT_HANDLE key_handle = CK_INVALID_HANDLE;
if (C_CreateObject(session.handle(),
attributes,
arraysize(attributes),
&key_handle) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to write key data: " << key_name;
return false;
}
return true;
}
bool Pkcs11KeyStore::Delete(bool is_user_specific,
const std::string& username,
const std::string& key_name) {
CK_SLOT_ID slot;
if (!GetUserSlot(is_user_specific, username, &slot))
return false;
ScopedSession session(slot);
if (!session.IsValid())
return false;
CK_OBJECT_HANDLE key_handle = FindObject(session.handle(), key_name);
if (key_handle != CK_INVALID_HANDLE) {
if (C_DestroyObject(session.handle(), key_handle) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to delete key data.";
return false;
}
}
return true;
}
bool Pkcs11KeyStore::DeleteByPrefix(bool is_user_specific,
const std::string& username,
const std::string& key_prefix) {
CK_SLOT_ID slot;
if (!GetUserSlot(is_user_specific, username, &slot))
return false;
ScopedSession session(slot);
if (!session.IsValid())
return false;
EnumObjectsCallback callback = base::Bind(
&Pkcs11KeyStore::DeleteIfMatchesPrefix,
base::Unretained(this),
session.handle(),
key_prefix);
if (!EnumObjects(session.handle(), callback)) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to delete key data.";
return false;
}
return true;
}
bool Pkcs11KeyStore::Register(bool is_user_specific,
const std::string& username,
const std::string& label,
const brillo::SecureBlob& private_key_blob,
const brillo::SecureBlob& public_key_der,
const brillo::SecureBlob& certificate) {
const CK_ATTRIBUTE_TYPE kKeyBlobAttribute = CKA_VENDOR_DEFINED + 1;
CK_SLOT_ID slot;
if (!GetUserSlot(is_user_specific, username, &slot))
return false;
ScopedSession session(slot);
if (!session.IsValid())
return false;
// Extract the modulus from the public key.
const unsigned char* asn1_ptr = public_key_der.data();
crypto::ScopedRSA public_key(d2i_RSAPublicKey(NULL,
&asn1_ptr,
public_key_der.size()));
if (!public_key.get()) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to decode public key.";
return false;
}
SecureBlob modulus(BN_num_bytes(public_key.get()->n));
int length = BN_bn2bin(public_key.get()->n, modulus.data());
if (length <= 0) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to extract public key modulus.";
return false;
}
modulus.resize(length);
// Construct a PKCS #11 template for the public key object.
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_KEY_TYPE key_type = CKK_RSA;
CK_OBJECT_CLASS public_key_class = CKO_PUBLIC_KEY;
SecureBlob id = CryptoLib::Sha1(modulus);
SecureBlob mutable_label(label);
CK_ULONG modulus_bits = modulus.size() * 8;
unsigned char public_exponent[] = {1, 0, 1};
CK_ATTRIBUTE public_key_attributes[] = {
{CKA_CLASS, &public_key_class, sizeof(public_key_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_DERIVE, &false_value, sizeof(false_value)},
{CKA_WRAP, &false_value, sizeof(false_value)},
{CKA_VERIFY, &true_value, sizeof(true_value)},
{CKA_VERIFY_RECOVER, &false_value, sizeof(false_value)},
{CKA_ENCRYPT, &false_value, sizeof(false_value)},
{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
{CKA_ID, id.data(), id.size()},
{CKA_LABEL, mutable_label.data(), mutable_label.size()},
{CKA_MODULUS_BITS, &modulus_bits, sizeof(modulus_bits)},
{CKA_PUBLIC_EXPONENT, public_exponent, arraysize(public_exponent)},
{CKA_MODULUS, modulus.data(), modulus.size()}
};
CK_OBJECT_HANDLE object_handle = CK_INVALID_HANDLE;
if (C_CreateObject(session.handle(),
public_key_attributes,
arraysize(public_key_attributes),
&object_handle) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to create public key object.";
return false;
}
// Construct a PKCS #11 template for the private key object.
CK_OBJECT_CLASS private_key_class = CKO_PRIVATE_KEY;
CK_ATTRIBUTE private_key_attributes[] = {
{CKA_CLASS, &private_key_class, sizeof(private_key_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &true_value, sizeof(true_value)},
{CKA_SENSITIVE, &true_value, sizeof(true_value)},
{CKA_EXTRACTABLE, &false_value, sizeof(false_value)},
{CKA_DERIVE, &false_value, sizeof(false_value)},
{CKA_UNWRAP, &false_value, sizeof(false_value)},
{CKA_SIGN, &true_value, sizeof(true_value)},
{CKA_SIGN_RECOVER, &false_value, sizeof(false_value)},
{CKA_DECRYPT, &false_value, sizeof(false_value)},
{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
{CKA_ID, id.data(), id.size()},
{CKA_LABEL, mutable_label.data(), mutable_label.size()},
{CKA_PUBLIC_EXPONENT, public_exponent, arraysize(public_exponent)},
{CKA_MODULUS, modulus.data(), modulus.size()},
{
kKeyBlobAttribute,
const_cast<uint8_t*>(private_key_blob.data()),
private_key_blob.size()
}
};
if (C_CreateObject(session.handle(),
private_key_attributes,
arraysize(private_key_attributes),
&object_handle) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to create private key object.";
return false;
}
if (!certificate.empty()) {
brillo::SecureBlob subject;
if (!GetCertificateSubject(certificate, &subject)) {
LOG(WARNING) << "Pkcs11KeyStore: Failed to find certificate subject.";
}
// Construct a PKCS #11 template for a certificate object.
SecureBlob mutable_certificate = certificate;
CK_OBJECT_CLASS certificate_class = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE certificate_type = CKC_X_509;
CK_ATTRIBUTE certificate_attributes[] = {
{CKA_CLASS, &certificate_class, sizeof(certificate_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &false_value, sizeof(false_value)},
{CKA_ID, id.data(), id.size()},
{CKA_LABEL, mutable_label.data(), mutable_label.size()},
{CKA_CERTIFICATE_TYPE, &certificate_type, sizeof(certificate_type)},
{CKA_SUBJECT, subject.data(), subject.size()},
{CKA_VALUE, mutable_certificate.data(), mutable_certificate.size()}
};
if (C_CreateObject(session.handle(),
certificate_attributes,
arraysize(certificate_attributes),
&object_handle) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to create certificate object.";
return false;
}
}
// Close all sessions in an attempt to trigger other modules to find the new
// objects.
C_CloseAllSessions(slot);
return true;
}
bool Pkcs11KeyStore::RegisterCertificate(
bool is_user_specific,
const std::string& username,
const brillo::SecureBlob& certificate) {
CK_SLOT_ID slot;
if (!GetUserSlot(is_user_specific, username, &slot))
return false;
ScopedSession session(slot);
if (!session.IsValid())
return false;
if (DoesCertificateExist(session.handle(), certificate)) {
LOG(INFO) << "Pkcs11KeyStore: Certificate already exists.";
return true;
}
brillo::SecureBlob subject;
if (!GetCertificateSubject(certificate, &subject)) {
LOG(WARNING) << "Pkcs11KeyStore: Failed to find certificate subject.";
}
// Construct a PKCS #11 template for a certificate object.
SecureBlob mutable_certificate = certificate;
CK_OBJECT_CLASS certificate_class = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE certificate_type = CKC_X_509;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_ATTRIBUTE certificate_attributes[] = {
{CKA_CLASS, &certificate_class, sizeof(certificate_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &false_value, sizeof(false_value)},
{CKA_CERTIFICATE_TYPE, &certificate_type, sizeof(certificate_type)},
{CKA_SUBJECT, subject.data(), subject.size()},
{CKA_VALUE, mutable_certificate.data(), mutable_certificate.size()}
};
CK_OBJECT_HANDLE object_handle = CK_INVALID_HANDLE;
if (C_CreateObject(session.handle(),
certificate_attributes,
arraysize(certificate_attributes),
&object_handle) != CKR_OK) {
LOG(ERROR) << "Pkcs11KeyStore: Failed to create certificate object.";
return false;
}
return true;
}
CK_OBJECT_HANDLE Pkcs11KeyStore::FindObject(CK_SESSION_HANDLE session_handle,
const std::string& key_name) {
// Assemble a search template.
CK_OBJECT_CLASS object_class = CKO_DATA;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_ATTRIBUTE attributes[] = {
{CKA_CLASS, &object_class, sizeof(object_class)},
{
CKA_LABEL,
string_as_array(const_cast<std::string*>(&key_name)),
key_name.size()
},
{
CKA_APPLICATION,
const_cast<char*>(kApplicationID),
arraysize(kApplicationID)
},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &true_value, sizeof(true_value)},
{CKA_MODIFIABLE, &false_value, sizeof(false_value)}
};
CK_OBJECT_HANDLE key_handle = CK_INVALID_HANDLE;
CK_ULONG count = 0;
if ((C_FindObjectsInit(session_handle,
attributes,
arraysize(attributes)) != CKR_OK) ||
(C_FindObjects(session_handle, &key_handle, 1, &count) != CKR_OK) ||
(C_FindObjectsFinal(session_handle) != CKR_OK)) {
LOG(ERROR) << "Key search failed: " << key_name;
return CK_INVALID_HANDLE;
}
if (count == 1)
return key_handle;
return CK_INVALID_HANDLE;
}
bool Pkcs11KeyStore::GetUserSlot(bool is_user_specific,
const std::string& username,
CK_SLOT_ID_PTR slot) {
const char kChapsDaemonName[] = "chaps";
const char kChapsSystemToken[] = "/var/lib/chaps";
FilePath token_path = is_user_specific ?
brillo::cryptohome::home::GetDaemonPath(username, kChapsDaemonName) :
FilePath(kChapsSystemToken);
return pkcs11_init_->GetTpmTokenSlotForPath(token_path, slot);
}
bool Pkcs11KeyStore::EnumObjects(
CK_SESSION_HANDLE session_handle,
const Pkcs11KeyStore::EnumObjectsCallback& callback) {
// Assemble a search template.
CK_OBJECT_CLASS object_class = CKO_DATA;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_ATTRIBUTE attributes[] = {
{CKA_CLASS, &object_class, sizeof(object_class)},
{
CKA_APPLICATION,
const_cast<char*>(kApplicationID),
arraysize(kApplicationID)
},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &true_value, sizeof(true_value)},
{CKA_MODIFIABLE, &false_value, sizeof(false_value)}
};
const CK_ULONG kMaxHandles = 100; // Arbitrary.
CK_OBJECT_HANDLE handles[kMaxHandles];
CK_ULONG count = 0;
if ((C_FindObjectsInit(session_handle,
attributes,
arraysize(attributes)) != CKR_OK) ||
(C_FindObjects(session_handle, handles, kMaxHandles, &count) != CKR_OK)) {
LOG(ERROR) << "Key search failed.";
return false;
}
while (count > 0) {
for (CK_ULONG i = 0; i < count; ++i) {
std::string key_name;
if (!GetKeyName(session_handle, handles[i], &key_name)) {
LOG(WARNING) << "Found key object but failed to get name.";
continue;
}
if (!callback.Run(key_name, handles[i]))
return false;
}
if (C_FindObjects(session_handle, handles, kMaxHandles, &count) != CKR_OK) {
LOG(ERROR) << "Key search continuation failed.";
return false;
}
}
if (C_FindObjectsFinal(session_handle) != CKR_OK) {
LOG(WARNING) << "Failed to finalize key search.";
}
return true;
}
bool Pkcs11KeyStore::GetKeyName(CK_SESSION_HANDLE session_handle,
CK_OBJECT_HANDLE object_handle,
std::string* key_name) {
CK_ATTRIBUTE attribute = {CKA_LABEL, NULL, 0};
if (C_GetAttributeValue(session_handle, object_handle, &attribute, 1) !=
CKR_OK) {
LOG(ERROR) << "C_GetAttributeValue(CKA_LABEL) [length] failed.";
return false;
}
key_name->resize(attribute.ulValueLen);
attribute.pValue = string_as_array(key_name);
if (C_GetAttributeValue(session_handle, object_handle, &attribute, 1) !=
CKR_OK) {
LOG(ERROR) << "C_GetAttributeValue(CKA_LABEL) failed.";
return false;
}
return true;
}
bool Pkcs11KeyStore::DeleteIfMatchesPrefix(CK_SESSION_HANDLE session_handle,
const std::string& key_prefix,
const std::string& key_name,
CK_OBJECT_HANDLE object_handle) {
if (key_name.find(key_prefix) == 0) {
if (C_DestroyObject(session_handle, object_handle) != CKR_OK) {
LOG(ERROR) << "C_DestroyObject failed.";
return false;
}
}
return true;
}
bool Pkcs11KeyStore::GetCertificateSubject(
const brillo::SecureBlob& certificate,
brillo::SecureBlob* subject) {
const unsigned char* asn1_ptr = certificate.data();
ScopedX509 x509(d2i_X509(NULL, &asn1_ptr, certificate.size()));
if (!x509.get() || !x509->cert_info || !x509->cert_info->subject) {
LOG(WARNING) << "Pkcs11KeyStore: Failed to decode certificate.";
return false;
}
unsigned char* buffer = NULL;
int length = i2d_X509_NAME(x509->cert_info->subject, &buffer);
crypto::ScopedOpenSSLBytes scoped_buffer(buffer);
if (length <= 0) {
LOG(WARNING) << "Pkcs11KeyStore: Failed to encode certificate subject.";
return false;
}
SecureBlob tmp(buffer, buffer + length);
subject->swap(tmp);
return true;
}
bool Pkcs11KeyStore::DoesCertificateExist(
CK_SESSION_HANDLE session_handle,
const brillo::SecureBlob& certificate) {
CK_OBJECT_CLASS object_class = CKO_CERTIFICATE;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
SecureBlob mutable_certificate = certificate;
CK_ATTRIBUTE attributes[] = {
{CKA_CLASS, &object_class, sizeof(object_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_PRIVATE, &false_value, sizeof(false_value)},
{CKA_VALUE, mutable_certificate.data(), mutable_certificate.size()}
};
CK_OBJECT_HANDLE object_handle = CK_INVALID_HANDLE;
CK_ULONG count = 0;
if ((C_FindObjectsInit(session_handle,
attributes,
arraysize(attributes)) != CKR_OK) ||
(C_FindObjects(session_handle, &object_handle, 1, &count) != CKR_OK) ||
(C_FindObjectsFinal(session_handle) != CKR_OK)) {
return false;
}
return (count > 0);
}
} // namespace cryptohome