blob: 4ee4714653e9132c65aef724d54f368891ea993c [file] [log] [blame]
// Copyright (c) 2010 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 "entd/pkcs11.h"
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <tr1/memory>
#include <base/basictypes.h>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/scoped_ptr.h>
#include <chromeos/utility.h>
#include <curl/curl.h>
#include <opencryptoki/pkcs11.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "entd/utils.h"
namespace entd {
// Class SlotObject declaration (implementation below)
// SlotObject JavaScript interface wrapper around SlotHandler.
class SlotObject : public JSObjectWrapper<SlotObject> {
public:
SlotObject() : slot_handler_(NULL) {}
SlotObject(const std::string& label, const std::string& key_id)
: label_(label), key_identifier_(key_id), slot_handler_(NULL) {}
virtual ~SlotObject() {}
virtual bool Initialize();
// JSObjectWrapper Interface
virtual bool ParseConstructorArgs(
v8::Handle<v8::Object> obj, const v8::Arguments& args);
static void SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object);
static const char* GetClassName() { return "SlotObject"; }
// Accessors
const std::string& label() const { return label_; }
const std::string& key_identifier() const { return key_identifier_; }
const std::string& passphrase() const { return passphrase_; }
std::string GetPublicKey() const {
return slot_handler()->GetPublicKey(label_);
}
SlotHandler* slot_handler() const { return slot_handler_; }
// Setters
// Stores the key identifier and passes it to the slot handler.
bool SetKeyIdentifier(const std::string& id) {
key_identifier_ = id;
return slot_handler_->SetKeyIdentifier(label_, id);
}
const void SetPassphrase(const std::string& passphrase) {
passphrase_ = passphrase;
}
void SetSlotHandler(SlotHandler* slot_handler) {
slot_handler_ = slot_handler;
}
private:
std::string label_;
std::string key_identifier_;
std::string passphrase_;
SlotHandler* slot_handler_;
DISALLOW_COPY_AND_ASSIGN(SlotObject);
};
// Class Certificate declaration (implementation below)
// Certificate JavaScript interface wrapper around CertificateHandler.
class Certificate : public JSObjectWrapper<Certificate> {
public:
Certificate() {}
virtual ~Certificate() {}
// JSObjectWrapper Interface
virtual bool ParseConstructorArgs(
v8::Handle<v8::Object> obj, const v8::Arguments& args);
static void SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object);
static const char* GetClassName() { return "Certificate"; }
// Accessors
const chromeos::Blob& certificate() const { return certificate_; }
const std::string& subject() const { return subject_; }
// Certificate handler management
static void InitCertificateHandler(CertificateHandler* handler) {
certificate_handler_ = handler;
}
static CertificateHandler* certificate_handler() {
return certificate_handler_;
}
private:
// The certificate may contain binary data, so store it as an array of bytes
// instead of a string.
chromeos::Blob certificate_;
std::string subject_;
static CertificateHandler* certificate_handler_;
DISALLOW_COPY_AND_ASSIGN(Certificate);
};
// Class CertificateHandlerOpenSsl
// Implements CSR generation using the openssl crypto library
//
// TODO(rginda): Refactor most of the implementation of this class out
// into an openssl_utils module.
class CertificateHandlerOpenSsl : public CertificateHandler {
public:
CertificateHandlerOpenSsl() {}
virtual ~CertificateHandlerOpenSsl() {}
virtual bool Initialize() { return true; }
virtual bool BuildCSR(const std::string& label,
const std::string& subject,
std::string* csr) {
LOG(INFO) << "Generating CSR.";
// Equivalent to:
// openssl req -new -batch -newkey rsa:2048 -subj {subject}
// -keyout {private_key} -pubkey -out {csr}
// -passout pass:{passphrase}
// Generate a private key in DER format and pass it to the slot handler
EVP_PKEY* pkey = EVP_PKEY_new();
RSA* rsa = NULL; // NOTE: This gets freed with pkey
bool res = pkey && GeneratePrivateKey(pkey, &rsa) &&
PrivateKeyToDER(rsa, &private_key_der_);
if (!res) {
LOG(ERROR) << "Error generating RSA key.";
EVP_PKEY_free(pkey);
return false;
}
// Generate a CSR request using the private key
X509_REQ* req = X509_REQ_new();
res = req && SetX509Req(pkey, subject, req);
EVP_PKEY_free(pkey);
if (!res) {
LOG(ERROR) << "Error generating X509 Request.";
X509_REQ_free(req);
return false;
}
// Extract the CSR and Public Key components in PEM format
res = X509ReqPublicKeyToPEM(req, &public_key_) &&
X509ReqToPEM(req, &csr_);
X509_REQ_free(req);
if (!res) {
LOG(ERROR) << "Error extracting data from X509 Request.";
return false;
}
// Store the private key in the slot handler (e.g. TPM device)
if (!slot_handler()->AddPrivateKey(label, subject, private_key_der_))
return false;
*csr = csr_;
return true;
}
virtual bool BuildCertificate(const std::string& content,
chromeos::Blob* certificate,
std::string* subject) {
LOG(INFO) << "Building Certificate.";
// Equivalent to:
// openssl x509 -inform PEM -in content -outform DER -subject subject
if (content.empty()) {
LOG(ERROR) << "BuildCertificate called with empty content";
return false;
}
// Convert 'content' to a DER Blob and extract the subject
X509* cert = NULL;
bool res = X509CertificateToDER(content, &cert, certificate)
&& X509CertificateToSubject(cert, subject);
X509_free(cert);
if (!res) {
LOG(ERROR) << "Error in X509 Certificate parsing.";
return false;
}
return true;
}
private:
// Utility class and functions for interfacing with the openssl BIO interface
// BioMem allocates an in-memory BIO instance and frees it on destruction.
//
// TODO(rginda): Remove static "Create" in favor of a per-instance
// "Initialize" returning a boolean, so callers can dump the scoped_pointers
// in favor of stack allocated objects.
class BioMem {
public:
static BioMem* Create() {
BioMem* biomem = new BioMem();
if (!biomem || !biomem->InitBio()) {
LOG(ERROR) << "Error initializing BIO";
delete biomem;
return NULL;
}
return biomem;
}
static BioMem* CreateFromString(const std::string& data) {
BioMem* biomem = new BioMem();
if (!biomem || !biomem->InitBioFromString(data)) {
LOG(ERROR) << "Error initializing BIO from string";
delete biomem;
return NULL;
}
return biomem;
}
~BioMem() {
BIO_free(bio_);
}
// Extract a std::string from the BIO instance
bool GetBIOString(std::string* str) {
char* mem = 0;
long bytes = BIO_get_mem_data(bio(), &mem);
if (bytes == 0 || !mem) {
return false;
}
*str = std::string(mem, mem + bytes);
return true;
}
// Extract a Blob from the BIO instance
bool GetBIOBlob(chromeos::Blob* der) {
char* mem = 0;
long bytes = BIO_get_mem_data(bio(), &mem);
if (bytes == 0 || !mem) {
return false;
}
unsigned char* umem = reinterpret_cast<unsigned char*>(mem);
*der = chromeos::Blob(umem, umem + bytes);
return true;
}
BIO* bio() const { return bio_; }
private:
BioMem() : bio_(NULL) {};
// The default init is typically used to receive then extract data
bool InitBio() {
bio_ = BIO_new(BIO_s_mem());
return (bio_ != NULL);
}
// The string init creates a BIO instance with the string contents
// to pass to openssl functions
bool InitBioFromString(const std::string& data) {
char* bytes = const_cast<char*>(data.c_str());
bio_ = BIO_new_mem_buf(bytes, data.size());
return (bio_ != NULL);
}
BIO* bio_;
};
// Private key related functions.
bool GeneratePrivateKey(EVP_PKEY* pkey, RSA** rsap) {
// Get the default RAND file name from the crypto library (~/.rnd)
char rand_file_name[kMaxFilePath];
if (RAND_file_name(rand_file_name, kMaxFilePath) == NULL)
return false;
// Expand the filename ($USERNAME -> user)
std::string rand_file_name_exp = utils::ExpandFilePath(rand_file_name);
// Try to load the filename returned. The RAND file is a nice way
// of stirring the RNG, but it's not necessary - OpenSSL knows how
// to use /dev/random and has already used it for seeding. If the
// RNG truly doesn't have enough entropy, the RSA_generate_key()
// call will fail.
if (RAND_load_file(rand_file_name_exp.c_str(), -1) == 0)
LOG(WARNING) << "Unable to load RAND file: " << rand_file_name_exp;
// Generate the RSA key and an EVP_PKEY wrapper
RSA* rsa = RSA_generate_key(kRsaKeyLength, kRsaKeyExponent, NULL, NULL);
if (!rsa)
return false;
if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
RSA_free(rsa); // rsa won't be freed with pkey if assign failed.
return false;
}
// Write to the RAND file
if (RAND_write_file(rand_file_name_exp.c_str()) == 0)
LOG(WARNING) << "Unable to write to RAND file: " << rand_file_name_exp;
*rsap = rsa;
return true;
}
bool PrivateKeyToDER(RSA* rsa, chromeos::Blob* der) {
scoped_ptr<BioMem> bio_mem(BioMem::Create());
if (!bio_mem.get())
return false;
bool res = i2d_RSAPrivateKey_bio(bio_mem->bio(), rsa)
&& bio_mem->GetBIOBlob(der);
return res;
}
// X509 Request generation helper functions
// Find the first instance of 'c' in string 's', skipping over escape
// sequences (e.g if c == '/' skip '\/')
std::string::size_type FindUnescaped(const std::string& s,
char c,
std::string::size_type start) {
if (start >= s.length())
return std::string::npos;
for (std::string::const_iterator iter = s.begin() + start;
iter != s.end(); ++iter) {
char c1 = *iter;
if (c1 == '\\') {
++iter; // skip the '\', the loop will skip the next char
if (iter == s.end())
break;
} else {
if (c1 == c)
return iter - s.begin();
}
}
return std::string::npos;
}
// Parse the subject and add the name/type pairs to an X509_NAME.
bool ParseSubject(const std::string& subject, X509_NAME* name) {
if (subject.empty())
return false;
std::string::size_type start = 0;
while (start != std::string::npos) {
if (subject[start] != '/') {
LOG(ERROR) << "Subject must start with '/'";
return false;
}
++start; // skip leading '/'
// Find the next '/' (or npos to indicate the end of the string)
std::string::size_type end = FindUnescaped(subject, '/', start);
// Build the token amd split it at '=' into type/value
std::string token;
if (end == std::string::npos)
token = subject.substr(start);
else
token = subject.substr(start, end - start);
if (token.empty()) {
LOG(ERROR) << "Subject contains an invalid entry at: " << start;
return false;
}
std::string::size_type eq = FindUnescaped(token, '=', 0);
if (eq == std::string::npos) {
LOG(ERROR) << "Subject entry is missing '=': '" << token << "'";
return false;
}
std::string type = token.substr(0, eq);
std::string value = token.substr(eq+1);
if (type.length() == 0) {
LOG(ERROR) << "Subject entry has no type: '" << token << "'";
return false;
}
// Add the type/value pair to the X509_NAME
const unsigned char* bytes =
reinterpret_cast<const unsigned char*>(value.c_str());
if (!X509_NAME_add_entry_by_txt(name, type.c_str(), MBSTRING_ASC,
bytes, value.size(), -1, 0)) {
return false;
}
start = end; // start at next '/' (or npos)
}
return true;
}
// X509 Request functions
bool SetX509Req(EVP_PKEY* pkey, const std::string& subject,
X509_REQ* req) {
if (!X509_REQ_set_version(req, 0L)) // version 1
return false;
X509_NAME* name = X509_NAME_new();
if (!name)
return false;
bool res = ParseSubject(subject, name)
&& X509_REQ_set_subject_name(req, name)
&& X509_REQ_set_pubkey(req, pkey)
&& X509_REQ_sign(req, pkey, EVP_sha1());
X509_NAME_free(name);
return res;
}
bool X509ReqPublicKeyToPEM(X509_REQ* req, std::string* pub_key_str) {
scoped_ptr<BioMem> bio_mem(BioMem::Create());
if (!bio_mem.get())
return false;
EVP_PKEY* pub_key = X509_REQ_get_pubkey(req);
if (!pub_key)
return false;
bool res = PEM_write_bio_PUBKEY(bio_mem->bio(), pub_key)
&& bio_mem->GetBIOString(pub_key_str);
EVP_PKEY_free(pub_key);
return res;
}
bool X509ReqToPEM(X509_REQ* req, std::string* req_str) {
scoped_ptr<BioMem> bio_mem (BioMem::Create());
if (!bio_mem.get())
return false;
bool res = PEM_write_bio_X509_REQ(bio_mem->bio(), req)
&& bio_mem->GetBIOString(req_str);
return res;
}
// X509 Certificte functions
bool X509CertificateToDER(const std::string& content,
X509** cert, chromeos::Blob* der) {
scoped_ptr<BioMem> bio_content(BioMem::CreateFromString(content));
if (!bio_content.get())
return false;
bool res = PEM_read_bio_X509(bio_content->bio(), cert, NULL, NULL);
if (!res)
return false;
scoped_ptr<BioMem> bio_cert(BioMem::Create());
if (!bio_cert.get())
return false;
res = i2d_X509_bio(bio_cert->bio(), *cert)
&& bio_cert->GetBIOBlob(der);
return res;
}
bool X509CertificateToSubject(X509* cert, std::string* subject) {
X509_NAME* name = X509_get_subject_name(cert);
const int NAME_BUF_LEN = 1024;
char name_buf[NAME_BUF_LEN];
if (!X509_NAME_oneline(name, name_buf, NAME_BUF_LEN))
return false;
subject->assign(name_buf);
return true;
}
static const long kRsaKeyLength;
static const long kRsaKeyExponent;
static const long kMaxFilePath;
std::string csr_;
std::string certificate_;
std::string public_key_;
chromeos::Blob private_key_der_;
DISALLOW_COPY_AND_ASSIGN(CertificateHandlerOpenSsl);
};
// Use 2048 bits for the key length, and an exponent of 0x10001.
const long CertificateHandlerOpenSsl::kRsaKeyLength = 2048;
const long CertificateHandlerOpenSsl::kRsaKeyExponent = 0x10001;
// Max number of chars for local array allocation for file paths.
const long CertificateHandlerOpenSsl::kMaxFilePath = 1024;
// Class SlotHandlerInMemory
// Stores the contents of slots in memory, used for testing the interface.
class SlotHandlerInMemory : public SlotHandler {
protected:
// Forward declarations and typedefs.
struct Object;
typedef std::map<std::string, std::tr1::shared_ptr<Object> > ObjectMap;
public:
SlotHandlerInMemory() { }
virtual bool Initialize() { return true; }
virtual bool SetUserPin(const std::string& pin) { return true; }
virtual bool SetKeyIdentifier(const std::string& label,
const std::string& keyid) {
Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "BuildSlotObject must be called before SetKeyIdentifier";
return false;
}
obj->key_identifier_ = keyid;
return true;
}
virtual bool BuildSlotObject(const std::string& label) {
if (label.empty()) {
LOG(ERROR) << "BuildSlotObject called with empty label.";
return false;
}
if (GetObject(label) != NULL) {
LOG(ERROR) << "BuildSlotObject called on existing object: " << label;
return false;
} else {
AddObject(label);
return true;
}
}
// No-op (just save the key identifier and passphrase.)
virtual bool GenerateKeyPair(const std::string& label,
const std::string& passphrase) {
Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "BuildSlotObject must be called before GenerateKeyPair";
return false;
}
obj->passphrase_ = passphrase;
obj->public_key_ = "<This is a public key>";
return true;
}
virtual bool AddCertificate(const std::string& label,
const chromeos::Blob& cert,
const std::string& subject) {
Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "BuildSlotObject must be called before AddCertificate";
return false;
}
obj->subject_ = subject;
obj->cert_ = cert;
return true;
}
virtual bool AddPrivateKey(const std::string& label,
const std::string& subject,
const chromeos::Blob& key) {
// Don't store the private key in memory.
return true;
}
virtual bool ReadObjectsFromSlot(Pkcs11* pkcs11) {
// In memory slot handler does not persist data anywhere, so there is
// no data to read.
return true;
}
virtual bool RemoveObjects(const std::string& label) {
DeleteObject(label);
return true;
}
virtual std::string GetKeyIdentifier(const std::string& label) const {
const Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "GetKeyIdentifier called on nonexistant object";
return NULL;
}
return obj->key_identifier_;
}
virtual std::string GetPassphrase(const std::string& label) const {
const Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "GetPassphrase called on nonexistant object";
return NULL;
}
return obj->passphrase_;
}
virtual std::string GetPublicKey(const std::string& label) const {
const Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "GetPublicKey called on nonexistant object";
return NULL;
}
return obj->public_key_;
}
virtual std::vector<std::string> GetObjectNames() const {
std::vector<std::string> objnames;
for (ObjectMap::const_iterator iter = objects_.begin();
iter != objects_.end(); ++iter) {
objnames.push_back(iter->first);
}
return objnames;
}
protected:
struct Object {
Object(const std::string& label) : label_(label) {}
std::string label_;
std::string key_identifier_;
std::string passphrase_;
std::string subject_;
std::string public_key_;
chromeos::Blob cert_;
};
const Object* GetObject(const std::string& label) const {
ObjectMap::const_iterator iter = objects_.find(label);
if (iter == objects_.end())
return NULL;
else
return iter->second.get();
}
Object* GetObject(const std::string& label) {
ObjectMap::iterator iter = objects_.find(label);
if (iter == objects_.end())
return NULL;
else
return iter->second.get();
}
Object* AddObject(const std::string& label) {
ObjectMap::iterator iter = objects_.find(label);
if (iter != objects_.end()) {
return iter->second.get();
} else {
Object* obj = new Object(label);
objects_.insert(std::make_pair(label, obj));
return obj;
}
}
bool DeleteObject(const std::string& label) {
ObjectMap::iterator iter = objects_.find(label);
if (iter == objects_.end()) {
return false;
} else {
objects_.erase(iter); // Will delete Object
return true;
}
}
ObjectMap objects_;
DISALLOW_COPY_AND_ASSIGN(SlotHandlerInMemory);
};
// Class SlotHandlerOpenCryptoki
// Inherits from SlotHandlerInMemory to share memory mapping of objects
class SlotHandlerOpenCryptoki : public SlotHandlerInMemory {
public:
SlotHandlerOpenCryptoki()
: user_pin_(""), slot_index_(0), slot_id_(0) { }
~SlotHandlerOpenCryptoki() {
C_Finalize(NULL);
}
virtual bool Initialize() {
CK_RV rv;
// Initialize opencryptoki
rv = C_Initialize(NULL);
if (rv != CKR_OK) {
// Note: This will happen if opencryptoki isn't properly set up.
LOG(ERROR) << "C_Initialize failed: " << rv;
return false;
}
CK_ULONG num_slots = 0;
CK_SLOT_ID_PTR slot_list = NULL;
// Get the list of slots
// The first call to C_GetSlotList fills in the number of slots.
rv = C_GetSlotList(0, NULL, &num_slots);
if (rv != CKR_OK) {
LOG(ERROR) << "C_GetSlotList(&num_slots) failed: " << rv;
return false;
}
if (num_slots == 0) {
LOG(ERROR) << "C_GetSlotList: 0 slots.";
return false;
}
slot_list = static_cast<CK_SLOT_ID_PTR>(
malloc(num_slots * sizeof (CK_SLOT_ID)));
if (slot_list == NULL) {
LOG(ERROR) << "Unable to allocate slots: " << num_slots;
return false;
}
// The second call to C_GetSlotList fills in the actual slot info.
rv = C_GetSlotList(0, slot_list, &num_slots);
if (rv != CKR_OK) {
LOG(ERROR) << "C_GetSlotList(&slot_list) failed. rv=" << rv
<< " num_slots=" << num_slots;
free(slot_list);
return false;
}
// Find a valid slot
slot_index_ = num_slots;
CK_MECHANISM_TYPE x509_mech_type = CKM_RSA_X_509;
CK_MECHANISM_INFO mech_info;
for (CK_ULONG i = 0; i < num_slots; ++i) {
CK_SLOT_ID slot_id = slot_list[i];
CK_TOKEN_INFO info;
rv = C_GetTokenInfo(i, &info);
if (rv != CKR_OK) {
LOG(INFO) << "Slot " << i << ": C_GetTokenInfo failed: " << rv;
continue;
}
// Skip any slots that can't handle RSA X.509 certificates.
if (C_GetMechanismInfo(slot_id, x509_mech_type, &mech_info) != CKR_OK) {
LOG(INFO) << "Slot " << i << ": C_GetMechanismInfo failed: " << rv;
continue;
}
// TODO(stevenjb):
// Here is where we would check any criteria other than support for x509.
// if ((mech_info.flags & CKF_HW) == 0) {
// LOG(INFO) << "Slot " << i << ": is not a hardware device, skipping.";
// continue;
// }
std::string label = std::string(reinterpret_cast<char*>(info.label));
std::string::size_type labeln = label.find_last_not_of(' ');
if (labeln != std::string::npos) ++labeln;
label = label.substr(0, labeln);
if (slot_index_ == num_slots) {
slot_index_ = i;
slot_id_ = slot_id;
LOG(INFO) << "*Slot " << i << ": '" << label << "'";
} else {
break; // Use the first valid slot we find
}
}
bool res;
if (slot_index_ == num_slots) {
LOG(ERROR) << "SlotHandlerOpenCryptoki Faied to find a valid slot.";
res = false;
} else {
LOG(INFO) << "SlotHandlerOpenCryptoki Initialized. Using slot: "
<< slot_index_;
res = true;
}
free(slot_list);
if (res) {
res = SlotHandlerInMemory::Initialize();
}
return res;
}
bool ReadObjectsFromSlot(Pkcs11* pkcs11) {
CK_SESSION_HANDLE session_handle = NULL;
CK_RV rv = C_OpenSession(slot_id_,
CKF_SERIAL_SESSION|CKF_RW_SESSION, NULL, NULL,
&session_handle);
if (rv != CKR_OK) {
LOG(ERROR) << "C_OpenSession failed, error: " << rv
<< ", slot: " << slot_index_;
return false;
}
// Gather a list of objects in the slot
int num_objects = 0;
rv = C_FindObjectsInit(session_handle, NULL, 0);
if (rv != CKR_OK) {
LOG(ERROR) << "C_FindObjectsInit failed: " << rv;
C_CloseSession(session_handle);
return false;
}
while (1) {
CK_ULONG count;
CK_OBJECT_HANDLE object;
rv = C_FindObjects(session_handle, &object, 1, &count);
if (rv != CKR_OK) {
LOG(ERROR) << "C_FindObjects failed: " << rv;
C_CloseSession(session_handle);
return false;
}
if (count == 0) {
// No more objects to read, exit loop.
break;
}
++num_objects;
const size_t LABEL_MAX_LENGTH = 256;
const size_t IDSTR_MAX_LENGTH = 8;
const size_t SUBJECT_MAX_LENGTH = 1024;
// Define a template with the values we are interested in
CK_OBJECT_CLASS obj_class;
char obj_label[LABEL_MAX_LENGTH];
CK_BYTE idstr[IDSTR_MAX_LENGTH];
CK_BYTE subject[SUBJECT_MAX_LENGTH];
CK_ATTRIBUTE obj_template[] = {
{ CKA_CLASS, &obj_class, sizeof(obj_class) },
{ CKA_LABEL, obj_label, LABEL_MAX_LENGTH },
{ CKA_ID, idstr, IDSTR_MAX_LENGTH },
{ CKA_SUBJECT, subject, SUBJECT_MAX_LENGTH },
};
int n_attr = sizeof(obj_template) / sizeof(CK_ATTRIBUTE);
// Read values into obj_template
rv = C_GetAttributeValue(session_handle, object, obj_template, n_attr);
if (rv != CKR_OK) {
LOG(ERROR) << "C_GetAttributeValue failed: " << rv
<< " obj: " << num_objects;
}
obj_class = *(static_cast<CK_OBJECT_CLASS*>(obj_template[0].pValue));
if (obj_class == CKO_CERTIFICATE ||
obj_class == CKO_PUBLIC_KEY ||
obj_class == CKO_PRIVATE_KEY) {
if (obj_template[1].ulValueLen > 0) {
// Only parse certificate and key objects with a valid label
std::string label = TemplateToString(obj_template[1]);
std::string idstr = GetKeyFromId(
static_cast<CK_BYTE*>(obj_template[2].pValue),
obj_template[2].ulValueLen);
std::string subject = TemplateToString(obj_template[3]);
Object* obj = AddObject(label);
if (!obj->key_identifier_.empty() && obj->key_identifier_ != idstr) {
LOG(WARNING) << "Object '" << label << "' "
<< "with mismatched key identifers: "
<< "'" << obj->key_identifier_ << "'"
<< " != '" << idstr << "'";
} else {
obj->key_identifier_ = idstr;
obj->subject_ = subject;
}
}
}
}
C_FindObjectsFinal(session_handle);
LOG(INFO) << "Found " << num_objects << " distinct objects in slot.";
// Iterate through the list of objects and generate
// SlotObject objects and insert them into pkcs11.slots.
for (ObjectMap::iterator iter = objects_.begin();
iter != objects_.end(); ++iter) {
Object* object = iter->second.get();
// Build a slot object
SlotObject* slot_object = new SlotObject(object->label_,
object->key_identifier_);
slot_object->Initialize();
slot_object->SetSlotHandler(pkcs11->slot_handler());
slot_object->obj()->Set(v8::String::NewSymbol("label"),
v8::String::New(object->label_.c_str()),
v8::ReadOnly);
slot_object->obj()->Set(v8::String::NewSymbol("keyIdentifier"),
v8::String::New(object->key_identifier_.c_str()));
// Build a certificate
Certificate* certificate = new Certificate();
certificate->Initialize();
certificate->obj()->Set(v8::String::NewSymbol("subject"),
v8::String::New(object->subject_.c_str()));
// Add the certificate to the slot object
slot_object->obj()->Set(v8::String::NewSymbol("certificate"),
certificate->obj());
// Add the object to pkcs11.slots ("slots[label] = obj")
pkcs11->AddJSSlotObject(slot_object);
}
C_CloseSession(session_handle);
return true;
}
// Returns false if the key is longer than a reasonable maximum length.
virtual bool SetKeyIdentifier(const std::string& label,
const std::string& keyid) {
const std::size_t MAX_KEY_LENGTH = 16;
if (keyid.length() > MAX_KEY_LENGTH)
return false;
return SlotHandlerInMemory::SetKeyIdentifier(label, keyid);
}
virtual bool SetUserPin(const std::string& pin) {
user_pin_ = pin;
return true;
}
virtual bool BuildSlotObject(const std::string& label) {
if (label.empty()) {
LOG(ERROR) << "BuildSlotObject called with empty label.";
return false;
}
if (GetObject(label) != NULL) {
LOG(INFO) << "Using existing SlotObject label: " << label;
return true;
} else {
AddObject(label);
LOG(INFO) << "New SlotObject label: " << label;
return true;
}
}
virtual bool AddCertificate(const std::string& label,
const chromeos::Blob& cert,
const std::string& subject) {
LOG(INFO) << "Adding Certificate.";
Object* object = GetObject(label);
if (!object) {
LOG(ERROR) << "BuildSlotObject must be called before AddCertificate.";
return false;
}
SessionHandle session(OpenSession(label));
if (!session.handle())
return false;
object->cert_ = cert;
object->subject_ = subject;
const size_t IDSTR_MAX_LENGTH = 8;
CK_BYTE idstr[IDSTR_MAX_LENGTH];
CK_ULONG idstrlen;
if (!GetKeyIdStr(object->key_identifier_,
&idstr[0], &idstrlen, IDSTR_MAX_LENGTH)) {
return false;
}
// Add the certifiacte object
CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE cert_type = CKC_X_509;
CK_BBOOL is_token = TRUE;
CK_ATTRIBUTE cert_template[16];
int n_attr = 0;
FillAttr(&cert_template[n_attr++], CKA_CLASS,
&cert_class, sizeof(cert_class));
FillAttr(&cert_template[n_attr++], CKA_CERTIFICATE_TYPE,
&cert_type, sizeof(cert_type));
FillAttr(&cert_template[n_attr++], CKA_TOKEN,
&is_token, sizeof(is_token));
FillStringAttr(&cert_template[n_attr++], CKA_LABEL, label);
FillStringAttr(&cert_template[n_attr++], CKA_SUBJECT, subject);
FillAttr(&cert_template[n_attr++], CKA_ID,
idstr, idstrlen);
FillAttr(&cert_template[n_attr++], CKA_VALUE,
(CK_VOID_PTR)(&(cert.front())), cert.size());
CK_OBJECT_HANDLE cert_obj;
CK_RV rv = C_CreateObject(session.handle(),
cert_template, n_attr, &cert_obj);
if (rv != CKR_OK) {
LOG(ERROR) << "C_CreateObject error: " << rv;
return false;
}
return true;
}
virtual bool AddPrivateKey(const std::string& label,
const std::string& subject,
const chromeos::Blob& key) {
LOG(INFO) << "Adding Private Key.";
Object* object = GetObject(label);
if (!object){
LOG(ERROR) << "BuildSlotObject must be called before AddPrivateKey.";
return false;
}
SessionHandle session(OpenSession(label));
if (!session.handle())
return false;
const size_t IDSTR_MAX_LENGTH = 8;
CK_BYTE idstr[IDSTR_MAX_LENGTH];
CK_ULONG idstrlen;
if (!GetKeyIdStr(object->key_identifier_,
&idstr[0], &idstrlen, IDSTR_MAX_LENGTH)) {
return false;
}
// Add the private key object
CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
CK_CERTIFICATE_TYPE key_type = CKK_RSA;
CK_BBOOL is_token = TRUE;
CK_BBOOL is_private = TRUE;
CK_BBOOL is_sensitive = TRUE;
RsaKeyInfo rsa;
if (!ParseRSAPrivateKey(key, &rsa)) {
return false;
}
CK_ATTRIBUTE key_template[32];
int n_attr = 0;
FillAttr(&key_template[n_attr++], CKA_CLASS,
&key_class, sizeof(key_class));
FillAttr(&key_template[n_attr++], CKA_KEY_TYPE,
&key_type, sizeof(key_type));
FillAttr(&key_template[n_attr++], CKA_TOKEN,
&is_token, sizeof(is_token));
FillAttr(&key_template[n_attr++], CKA_PRIVATE,
&is_token, sizeof(is_private));
FillAttr(&key_template[n_attr++], CKA_SENSITIVE,
&is_token, sizeof(is_sensitive));
FillStringAttr(&key_template[n_attr++], CKA_LABEL, label);
FillAttr(&key_template[n_attr++], CKA_ID,
idstr, idstrlen);
FillStringAttr(&key_template[n_attr++], CKA_SUBJECT, subject);
FillAttr(&key_template[n_attr++], CKA_MODULUS,
rsa.modulus, rsa.modulus_len);
FillAttr(&key_template[n_attr++], CKA_PUBLIC_EXPONENT,
rsa.public_exp, rsa.public_exp_len);
FillAttr(&key_template[n_attr++], CKA_PRIVATE_EXPONENT,
rsa.private_exp, rsa.private_exp_len);
FillAttr(&key_template[n_attr++], CKA_PRIME_1,
rsa.prime_1, rsa.prime_1_len);
FillAttr(&key_template[n_attr++], CKA_PRIME_2,
rsa.prime_2, rsa.prime_2_len);
FillAttr(&key_template[n_attr++], CKA_EXPONENT_1,
rsa.exp_1, rsa.exp_1_len);
FillAttr(&key_template[n_attr++], CKA_EXPONENT_2,
rsa.exp_2, rsa.exp_2_len);
FillAttr(&key_template[n_attr++], CKA_COEFFICIENT,
rsa.coefficient, rsa.coefficient_len);
CK_OBJECT_HANDLE key_obj;
CK_RV rv = C_CreateObject(session.handle(),
key_template, n_attr, &key_obj);
if (rv != CKR_OK) {
LOG(ERROR) << "C_CreateObject error: " << rv;
return false;
}
return true;
}
// TODO(rginda): Refactor so that the SSL based keypair generation actually
// happens here, rather than in the certificate object.
//
// See the NOTE at the bottom of this file for a
// close-but-still-broken implementation pkcs11-only keypair generation.
virtual bool GenerateKeyPair(const std::string& label,
const std::string& passphrase) {
Object* obj = GetObject(label);
if (!obj) {
LOG(WARNING) << "BuildSlotObject must be called before GenerateKeyPair";
return false;
}
obj->passphrase_ = passphrase;
return true;
}
// Removes all objects in the slot matching 'label'
// Note: may be called before BuildSlotObject(), but we still need to
// be logged in so that we can remove matching private keys.
virtual bool RemoveObjects(const std::string& label) {
LOG(INFO) << "Removing Objects matching: " << label;
SessionHandle session(OpenSession(label));
if (!session.handle())
return false;
CK_ATTRIBUTE object_template[4];
int n_attr = 0;
FillStringAttr(&object_template[n_attr++], CKA_LABEL, label);
CK_RV rv = C_FindObjectsInit(session.handle(), object_template, n_attr);
if (rv != CKR_OK) {
LOG(ERROR) << "C_FindObjectsInit failed: " << rv;
return false;
}
bool res = true;
int num_objects = 0;
while (1) {
CK_ULONG count;
CK_OBJECT_HANDLE object;
rv = C_FindObjects(session.handle(), &object, 1, &count);
if (rv != CKR_OK) {
LOG(ERROR) << "C_FindObjects failed: " << rv;
res = false;
break;
}
if (count == 0) {
break;
}
++num_objects;
rv = C_DestroyObject(session.handle(), object);
if (rv != CKR_OK) {
LOG(ERROR) << "C_DestroyObject failed: " << rv
<< " obj: " << num_objects;
res = false;
break;
}
}
LOG(INFO) << " Removed " << num_objects << " objects.";
C_FindObjectsFinal(session.handle());
return res;
}
private:
// Class to close sessions we have opened on destruction
class SessionHandle {
public:
SessionHandle(CK_SESSION_HANDLE handle) : handle_(handle) {}
~SessionHandle() {
if (handle_)
C_CloseSession(handle_);
}
CK_SESSION_HANDLE handle() { return handle_; }
private:
CK_SESSION_HANDLE handle_;
};
CK_SESSION_HANDLE OpenSession(const std::string& label) {
CK_SESSION_HANDLE handle = NULL;
CK_RV rv = C_OpenSession(slot_id_,
CKF_SERIAL_SESSION|CKF_RW_SESSION, NULL, NULL,
&handle);
if (rv != CKR_OK) {
LOG(ERROR) << "C_OpenSession failed, error: " << rv
<< " label: " << label << " slot: " << slot_index_;
return NULL;
}
CK_UTF8CHAR* pin_ptr =
reinterpret_cast<unsigned char*>(const_cast<char*>(user_pin_.c_str()));
rv = C_Login(handle, CKU_USER,
pin_ptr, user_pin_.length());
if (rv != CKR_OK) {
LOG(ERROR) << "C_Login failed, error: " << rv
<< " label: " << label << " pin: " << user_pin_;
return NULL;
}
return handle;
}
std::string TemplateToString(const CK_ATTRIBUTE& attr) {
const char* value = static_cast<const char*>(attr.pValue);
return std::string(value, attr.ulValueLen);
}
// openssl crypto library interface
struct RsaKeyInfo {
unsigned char *modulus;
int modulus_len;
unsigned char *public_exp;
int public_exp_len;
unsigned char *private_exp;
int private_exp_len;
unsigned char *prime_1;
int prime_1_len;
unsigned char *prime_2;
int prime_2_len;
unsigned char *exp_1;
int exp_1_len;
unsigned char *exp_2;
int exp_2_len;
unsigned char *coefficient;
int coefficient_len;
};
bool RSAGetBN(const BIGNUM* component , unsigned char** dest, int* len) {
unsigned char* vec = NULL;
int bytes = BN_num_bytes(component);
if (bytes) {
vec = static_cast<unsigned char*>(malloc(bytes));
}
if (!vec) {
LOG(ERROR) << "Error allocating memory while parsing RSA key.";
return false;
}
int length = BN_bn2bin(component, vec);
if (!length) {
LOG(ERROR) << "Error parsing component in RSA key.";
return false;
}
*len = length;
*dest = vec;
return true;
}
bool ParseRSAPrivateKey(const chromeos::Blob& data, RsaKeyInfo* info) {
const unsigned char *p = &(data.front());
RSA* key = d2i_RSAPrivateKey(NULL, &p, data.size());
if (!key) {
LOG(ERROR) << "OpenSSL error during RSA private key parsing.";
return false;
}
bool res =
RSAGetBN(key->n, &info->modulus, &info->modulus_len) &&
RSAGetBN(key->e, &info->public_exp, &info->public_exp_len) &&
RSAGetBN(key->d, &info->private_exp, &info->private_exp_len) &&
RSAGetBN(key->p, &info->prime_1, &info->prime_1_len) &&
RSAGetBN(key->q, &info->prime_2, &info->prime_2_len) &&
RSAGetBN(key->dmp1, &info->exp_1, &info->exp_1_len) &&
RSAGetBN(key->dmq1, &info->exp_2, &info->exp_2_len) &&
RSAGetBN(key->iqmp, &info->coefficient, &info->coefficient_len);
return res;
}
// Note: use casting to store const values as non const.
// This avoids using lots of casts or needlessly copying the contents
// of strings, but be careful not to use this to receive data
// into a string or other const array.
void FillStringAttr(CK_ATTRIBUTE* attr, CK_ATTRIBUTE_TYPE type,
const std::string& str) {
attr->type = type;
attr->pValue = const_cast<char*>(str.c_str());
attr->ulValueLen = str.length();
}
void FillAttr(CK_ATTRIBUTE* attr, CK_ATTRIBUTE_TYPE type,
void* value, CK_ULONG length) {
attr->type = type;
attr->pValue = value;
attr->ulValueLen = length;
}
// Converts the string "0123abcd" to CK_BYTE[] { 01,23,ab,cd }
// If the string is too long, *idstrlenp is set to 0 and false is returned.
// Invalid characters are clamped to [a,f]. "123abz@" -> { 01,23,ab,ff }
bool GetKeyIdStr(const std::string& keyid_in,
CK_BYTE* idstrp, CK_ULONG *idstrlenp, size_t max_length) {
std::string keyid;
if (keyid_in.size() & 1) {
keyid.push_back('0');
keyid.append(keyid_in);
} else {
keyid = keyid_in;
}
chromeos::Blob bytes = chromeos::AsciiDecode(keyid);
if (bytes.size() > max_length) {
LOG(ERROR) << "Too many characters in key: " << keyid;
*idstrlenp = 0;
return false;
}
memcpy(idstrp, &bytes.front(), bytes.size());
*idstrlenp = bytes.size();
return true;
}
// Inverse of GetKeyIdStr
std::string GetKeyFromId(CK_BYTE* idstrp, CK_ULONG idstrlen) {
chromeos::Blob bytes = chromeos::Blob(idstrp, idstrp+idstrlen);
return chromeos::AsciiEncode(bytes);
}
std::string user_pin_;
CK_ULONG slot_index_;
CK_SLOT_ID slot_id_;
DISALLOW_COPY_AND_ASSIGN(SlotHandlerOpenCryptoki);
};
// Class CSR
// CSR JavaScript interface wrapper around CertificateHandler.
class CSR : public JSObjectWrapper<CSR> {
public:
CSR() { }
virtual ~CSR() {}
// JSObjectWrapper Interface
virtual bool ParseConstructorArgs(
v8::Handle<v8::Object> obj, const v8::Arguments& args);
static void SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object);
static const char* GetClassName() { return "CSR"; }
// Accessor
const std::string& request() const { return request_; }
// Certificate handler management
static void InitCertificateHandler(CertificateHandler* cert_handler) {
certificate_handler_ = cert_handler;
}
static CertificateHandler* certificate_handler() {
return certificate_handler_;
}
private:
std::string request_;
static CertificateHandler* certificate_handler_;
DISALLOW_COPY_AND_ASSIGN(CSR);
};
// static
CertificateHandler* CSR::certificate_handler_ = NULL;
// Base64 encoded version of the CSR
static v8::Handle<v8::Value> dispatch_CSRToString(const v8::Arguments& args) {
CSR* csr = CSR::Unwrap(args.This());
if (csr == NULL)
return utils::ThrowV8Exception("Method called on incorrect object type.");
v8::Local<v8::String> res = v8::String::New(csr->request().c_str());
return res;
}
// static
void CSR::SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object) {
template_object->Set(v8::String::NewSymbol("toString"),
v8::FunctionTemplate::New(dispatch_CSRToString),
v8::ReadOnly);
}
// virtual
bool CSR::ParseConstructorArgs(
v8::Handle<v8::Object> obj, const v8::Arguments& args) {
if (args.Length() < 1) {
utils::ThrowV8Exception("Not enough parameters.");
return false;
}
// Extract the SlotObject
SlotObject* slot_object =
reinterpret_cast<SlotObject*>(v8::External::Unwrap(args.Data()));
if (!slot_object) {
utils::ThrowV8Exception("Data argument is not a SlotObject.");
return false;
}
// Argument 0 is the subject
v8::Handle<v8::Value> subject = args[0];
obj->Set(v8::String::NewSymbol("subject"), subject->ToString());
// Build the CSR request
if (!certificate_handler()->BuildCSR(slot_object->label(),
utils::ValueAsUtf8String(subject),
&request_)) {
utils::ThrowV8Exception("Error building CSR.");
return false;
}
return true;
}
// Class Certificate Implementation
// static
CertificateHandler* Certificate::certificate_handler_ = NULL;
// Returns the contents of the Certificate as a string.
// Note: if this is binary and not Base64, results may be unexpected.
// (i.e. don't call this unles you expect the contents to be a string)
static v8::Handle<v8::Value> dispatch_CertificateToString(
const v8::Arguments& args) {
Certificate* cert = Certificate::Unwrap(args.This());
const chromeos::Blob& certdata = cert->certificate();
const char* certstrptr = reinterpret_cast<const char*>(&(certdata.front()));
v8::Local<v8::String> res = v8::String::New(certstrptr);
return res;
}
// static
void Certificate::SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object){
template_object->Set(v8::String::NewSymbol("toString"),
v8::FunctionTemplate::New(dispatch_CertificateToString),
v8::ReadOnly);
}
// virtual
bool Certificate::ParseConstructorArgs(v8::Handle<v8::Object> obj,
const v8::Arguments& args) {
// Extract the pkcs11 object
Pkcs11* pkcs11 = reinterpret_cast<Pkcs11*>(v8::External::Unwrap(args.Data()));
if (!pkcs11) {
utils::ThrowV8Exception("Data argument is not a Pkcs11 object.");
return false;
}
if (!pkcs11->IsReady()) {
utils::ThrowV8Exception("Pkcs11 object is not ready.");
return false;
}
if (args.Length() < 1) {
utils::ThrowV8Exception("Not enough parameters.");
return false;
}
// Argument is the contents (passed to BuildCertificate)
v8::Handle<v8::Value> content = args[0];
// Generate the Certificate data
bool res = certificate_handler()->BuildCertificate(
utils::ValueAsUtf8String(content), &certificate_, &subject_);
if (!res) {
utils::ThrowV8Exception("Unable to build certificate.");
return false;
}
// Set the 'subject' property to the certificate subject
obj->Set(v8::String::NewSymbol("subject"),
v8::String::New(subject_.c_str()));
return true;
}
// Class SlotObject Implementation
bool SlotObject::Initialize() {
// Bind "CSR" here so we can associate this with it
v8::Handle<v8::FunctionTemplate> t = GetTemplate();
v8::Handle<v8::ObjectTemplate> t_obj = t->InstanceTemplate();
t_obj->Set(v8::String::NewSymbol("CSR"),
v8::FunctionTemplate::New(CSR::Construct,
v8::External::Wrap(this)),
v8::ReadOnly);
// Build and initialize the V8 object.
return JSObjectWrapper<SlotObject>::Initialize();
}
static v8::Handle<v8::Value> dispatch_generateKeyPair(
const v8::Arguments& args) {
// Get the arguments.
SlotObject* slot = SlotObject::Unwrap(args.This());
if (slot == NULL)
return utils::ThrowV8Exception("Method called on incorrect object type.");
if (args.Length() < 1)
return utils::ThrowV8Exception("Not enough parameters.");
// Argument 0 is the key identifier
v8::Handle<v8::String> v8keyid = args[0]->ToString();
std::string keyid = std::string(*v8::String::Utf8Value(v8keyid));
bool res = slot->SetKeyIdentifier(keyid);
if (!res)
return utils::ThrowV8Exception(std::string("Invalid key id: ") + keyid);
// Set the JS keyIdentifier property
slot->obj()->Set(v8::String::NewSymbol("keyIdentifier"), v8keyid);
// Argumnet 1 is the passphrase (optional)
std::string passphrase; // defaults to empty
if (!args[1]->IsUndefined()) {
passphrase = utils::ValueAsUtf8String(args[1]);
slot->SetPassphrase(passphrase);
}
// Access the slot handler and generate the public/private key pair
if (!slot->slot_handler()->GenerateKeyPair(slot->label(),
slot->passphrase())) {
return utils::ThrowV8Exception("Unable to build key pair");
}
return v8::True();
}
static v8::Handle<v8::Value> dispatch_addCertificate(
const v8::Arguments& args) {
// Get the arguments.
SlotObject* slot = SlotObject::Unwrap(args.This());
if (slot == NULL)
return utils::ThrowV8Exception("Method called on incorrect object type.");
if (args.Length() < 1)
return utils::ThrowV8Exception("Not enough parameters.");
// Argument is the Certificate object.
Certificate* cert = Certificate::GetFromArg(args, 0);
if (cert == NULL)
return utils::ThrowV8Exception("Invalid Certificate object");
// Access the slot handler and add the Certificate
if (!slot->slot_handler()->AddCertificate(slot->label(),
cert->certificate(),
cert->subject())) {
return utils::ThrowV8Exception("PKCS11 device error.");
}
// Add the certificate to the JS object
slot->obj()->Set(v8::String::NewSymbol("certificate"), cert->obj());
return v8::True();
}
// static
void SlotObject::SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object) {
// "CSR" is set in Initialize so it can include
// a pointer back to the SlotObject.
template_object->Set(v8::String::NewSymbol("generateKeyPair"),
v8::FunctionTemplate::New(dispatch_generateKeyPair),
v8::ReadOnly);
template_object->Set(v8::String::NewSymbol("addCertificate"),
v8::FunctionTemplate::New(dispatch_addCertificate),
v8::ReadOnly);
}
// virtual
bool SlotObject::ParseConstructorArgs(
v8::Handle<v8::Object> obj, const v8::Arguments& args) {
// Extract the pkcs11 object
Pkcs11* pkcs11 = reinterpret_cast<Pkcs11*>(v8::External::Unwrap(args.Data()));
if (!pkcs11) {
utils::ThrowV8Exception("Data argument is not a Pkcs11 object.");
return false;
}
if (!pkcs11->IsReady()) {
utils::ThrowV8Exception("Pkcs11 object is not ready.");
return false;
}
if (args.Length() < 1) {
utils::ThrowV8Exception("Not enough parameters.");
return false;
}
SetSlotHandler(pkcs11->slot_handler());
// Argument is the label; make it ReadOnly so it can not be changed later.
v8::Handle<v8::String> v8label = args[0]->ToString();
obj->Set(v8::String::NewSymbol("label"), v8label, v8::ReadOnly);
label_ = std::string(*v8::String::Utf8Value(v8label));
// Build the SlotObject
if (!pkcs11->slot_handler()->BuildSlotObject(label_)) {
utils::ThrowV8Exception("Unable to build SlotObject");
return false;
}
// Add this to pkcs11.slots ("slots[label] = obj")
pkcs11->AddJSSlotObject(this);
return true;
}
// Class Pkcs11
Pkcs11::Pkcs11()
: JSObjectWrapper<Pkcs11>(),
certificate_handler_(NULL),
slot_handler_(NULL),
handlers_ready_(false) {
}
bool Pkcs11::enable_opencryptoki = true;
Pkcs11::~Pkcs11() {
CleanupTemplate(); // We only have one Pkcs11 instance.
}
bool Pkcs11::Initialize() {
// Initialize the SlotObject template here so that we can embed 'this'.
v8::Handle<v8::FunctionTemplate> t = GetTemplate();
v8::Handle<v8::ObjectTemplate> t_obj = t->InstanceTemplate();
// These properties are set here because they need a reference to "this",
// which isn't available in the static SetTemplateBindings method.
t_obj->Set(v8::String::NewSymbol("SlotObject"),
v8::FunctionTemplate::New(SlotObject::Construct,
v8::External::Wrap(this)),
v8::ReadOnly);
t_obj->Set(v8::String::NewSymbol("Certificate"),
v8::FunctionTemplate::New(Certificate::Construct,
v8::External::Wrap(this)),
v8::ReadOnly);
// Build and initialize the V8 object.
if (!JSObjectWrapper<Pkcs11>::Initialize())
return false;
// InitializeCrypto may fail if the requisite crypto daemons aren't running.
// We try to setup here to save the caller the trouble, but if it fails
// we don't want to claim the whole initialization failed, so we ignore
// the return vale.
InitializeCrypto();
return true;
}
// Called to initialize the certificate and slot handlers. This may fail if
// the pkcs11 prerequisites aren't yet available (for example, the TPM hasn't
// been owned, or the token hasn't been initialized.) If it fails, you should
// be free to try again later.
bool Pkcs11::InitializeCrypto() {
if (handlers_ready_)
return true;
certificate_handler_.reset(new CertificateHandlerOpenSsl());
if (!certificate_handler_.get() || !certificate_handler_->Initialize()) {
LOG(WARNING) << "Certificate Handler failed to initialize.";
certificate_handler_.reset();
slot_handler_.reset();
return false;
}
if (Pkcs11::enable_opencryptoki) {
LOG(INFO) << "Initializing opencryptoki slot handler.";
slot_handler_.reset(new SlotHandlerOpenCryptoki());
} else {
LOG(INFO) << "Initializing in-memory slot handler.";
slot_handler_.reset(new SlotHandlerInMemory());
}
if (!slot_handler_.get() || !slot_handler_->Initialize()) {
LOG(WARNING) << "Slot Handler failed to initialize.";
certificate_handler_.reset();
slot_handler_.reset();
return false;
}
slot_handler_->ReadObjectsFromSlot(this);
// The certificate handler may need access to the slot handler.
certificate_handler_->SetSlotHandler(slot_handler());
// Set up the CSR and Certificate sub-classes with the certificate handler.
CSR::InitCertificateHandler(certificate_handler_.get());
Certificate::InitCertificateHandler(certificate_handler_.get());
handlers_ready_ = true;
return true;
}
// pkcs11[slot_object->label()] = slot_object->obj()
bool Pkcs11::AddJSSlotObject(const SlotObject* slot_object) {
v8::Local<v8::Value> slotsvalue = obj()->Get(v8::String::NewSymbol("slots"));
if (slotsvalue.IsEmpty() || !slotsvalue->IsObject()) {
LOG(ERROR) << "'slots' is not an object in pkcs11";
return false;
}
v8::Local<v8::Object> slots = v8::Local<v8::Object>::Cast(slotsvalue);
v8::Local<v8::String> v8label = v8::String::New(slot_object->label().c_str());
slots->Set(v8label, slot_object->obj());
return true;
}
// delete pkcs11.slots[label]")
bool Pkcs11::RemoveJSSlotObject(const std::string& label) {
v8::Local<v8::Value> slotsvalue = obj()->Get(v8::String::NewSymbol("slots"));
if (slotsvalue.IsEmpty() || !slotsvalue->IsObject()) {
LOG(ERROR) << "'slots' is not an object in pkcs11";
return false;
}
v8::Local<v8::Object> slots = v8::Local<v8::Object>::Cast(slotsvalue);
v8::Local<v8::String> v8label = v8::String::New(label.c_str());
slots->Delete(v8label);
return true;
}
static v8::Handle<v8::Value> dispatch_setUserPin(
const v8::Arguments& args) {
// Get the arguments.
Pkcs11* pkcs11 = Pkcs11::Unwrap(args.This());
if (!pkcs11)
return utils::ThrowV8Exception("Method called on incorrect object");
if (!pkcs11->IsReady())
return utils::ThrowV8Exception("Pkcs11 object is not ready");
if (args.Length() < 1)
return utils::ThrowV8Exception("Not enough parameters.");
// Argument is the pin
std::string pin = utils::ValueAsUtf8String(args[0]);
if (!pkcs11->slot_handler()->SetUserPin(pin))
return utils::ThrowV8Exception("PKCS11 device error");
return v8::True();
}
static v8::Handle<v8::Value> dispatch_removeSlotObject(
const v8::Arguments& args) {
// Get the pkcs11 object.
Pkcs11* pkcs11 = Pkcs11::Unwrap(args.This());
if (!pkcs11)
return utils::ThrowV8Exception("Method called on incorrect object");
if (!pkcs11->IsReady())
return utils::ThrowV8Exception("Pkcs11 object is not ready");
if (args.Length() < 1)
return utils::ThrowV8Exception("Not enough parameters");
// Argument 0 is a label.
std::string label = utils::ValueAsUtf8String(args[0]);
// Remove the contents of the slot in the slot handler.
if (!pkcs11->slot_handler()->RemoveObjects(label))
return utils::ThrowV8Exception("PKCS11 device error");
// Remove entry from pkcs11.slots
pkcs11->RemoveJSSlotObject(label);
return v8::True();
}
// Called by v8 when someone trys to read pkcs11.isReady.
v8::Handle<v8::Value> dispatch_GetIsReady(v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
Pkcs11* pkcs11 = Pkcs11::Unwrap(info.Holder());
if (!pkcs11)
return utils::ThrowV8Exception("Method called on incorrect object");
if (pkcs11->IsReady() || pkcs11->InitializeCrypto())
return v8::True();
return v8::False();
}
// Called by v8 when someone trys write to pkcs11.isReady.
void dispatch_SetReadOnly(v8::Local<v8::String> name,
v8::Local<v8::Value> value,
const v8::AccessorInfo& info) {
utils::ThrowV8Exception("Attempt to set read-only property");
}
// static
void Pkcs11::SetTemplateBindings(
v8::Handle<v8::ObjectTemplate> template_object) {
// "SlotObject" gets setup specially in Initialize();
template_object->Set(v8::String::NewSymbol("setUserPin"),
v8::FunctionTemplate::New(dispatch_setUserPin),
v8::ReadOnly);
template_object->Set(v8::String::NewSymbol("remove"),
v8::FunctionTemplate::New(dispatch_removeSlotObject),
v8::ReadOnly);
template_object->Set(v8::String::NewSymbol("slots"),
v8::Object::New(),
v8::ReadOnly);
template_object->SetAccessor(v8::String::NewSymbol("isReady"),
dispatch_GetIsReady,
dispatch_SetReadOnly);
}
#if 0
// NOTE(rginda): This could potentially be
// SlotHandlerOpencryptoki::GenerateKeyPair, except it's known to not
// work. The symptom is that it generates broken keys with a "0" exponent.
// The guess is that this is due to a bug in opencryptoki, though it could
// also be a bug in this chunk of code. For now, we just generate keypairs
// with the vanilla OpenSSL libraries, store them under opencryptoki,
// and toss out the plaintext keypair.
virtual bool GenerateKeyPair(const std::string& label,
const std::string& passphrase) {
LOG(INFO) << "Generating Key Pair.";
Object* object = GetObject(label);
if (!object) {
LOG(ERROR) << "BuildSlotObject must be called before GenerateKeyPair.";
return false;
}
SessionHandle session(OpenSession(label));
if (!session.handle())
return false;
CK_OBJECT_CLASS pubkey_class = CKO_PUBLIC_KEY;
CK_BBOOL is_token = TRUE;
CK_BBOOL can_encrypt = TRUE;
CK_BBOOL can_verify = TRUE;
CK_BBOOL can_wrap = TRUE;
CK_ULONG modulusBits = 2048;
CK_BYTE publicExponent[] = { 0x01, 0x00, 0x01 }; // 65537 in bytes
const size_t IDSTR_MAX_LENGTH = 8;
CK_BYTE idstr[IDSTR_MAX_LENGTH];
CK_ULONG idstrlen;
if (!GetKeyIdStr(object->key_identifier_,
&idstr[0], &idstrlen, IDSTR_MAX_LENGTH)) {
return false;
}
int public_key_n_attr = 0;
CK_ATTRIBUTE public_key_template[16];
FillAttr(&public_key_template[public_key_n_attr++], CKA_CLASS,
&pubkey_class, sizeof(pubkey_class));
FillAttr(&public_key_template[public_key_n_attr++], CKA_TOKEN,
&is_token, sizeof(is_token));
FillAttr(&public_key_template[public_key_n_attr++], CKA_ENCRYPT,
&can_encrypt, sizeof(can_encrypt));
FillAttr(&public_key_template[public_key_n_attr++], CKA_VERIFY,
&can_verify, sizeof(can_verify));
FillAttr(&public_key_template[public_key_n_attr++], CKA_WRAP,
&can_wrap, sizeof(can_wrap));
FillAttr(&public_key_template[public_key_n_attr++], CKA_MODULUS_BITS,
&modulusBits, sizeof(modulusBits));
FillAttr(&public_key_template[public_key_n_attr++], CKA_PUBLIC_EXPONENT,
publicExponent, sizeof(publicExponent));
FillStringAttr(&public_key_template[public_key_n_attr++], CKA_LABEL,
label);
FillAttr(&public_key_template[public_key_n_attr++], CKA_ID,
idstr, idstrlen);
CK_OBJECT_CLASS privkey_class = CKO_PRIVATE_KEY;
CK_BBOOL is_private = TRUE;
CK_BBOOL is_sensitive = TRUE;
CK_BBOOL can_decrypt = TRUE;
CK_BBOOL can_sign = TRUE;
CK_BBOOL can_unwrap = TRUE;
int private_key_n_attr = 0;
CK_ATTRIBUTE private_key_template[16];
FillAttr(&private_key_template[private_key_n_attr++], CKA_CLASS,
&privkey_class, sizeof(privkey_class));
FillAttr(&private_key_template[private_key_n_attr++], CKA_TOKEN,
&is_token, sizeof(is_token));
FillAttr(&private_key_template[private_key_n_attr++], CKA_PRIVATE,
&is_private, sizeof(is_private));
FillAttr(&private_key_template[private_key_n_attr++], CKA_SENSITIVE,
&is_sensitive, sizeof(is_sensitive));
FillAttr(&private_key_template[private_key_n_attr++], CKA_DECRYPT,
&can_decrypt, sizeof(can_decrypt));
FillAttr(&private_key_template[private_key_n_attr++], CKA_SIGN,
&can_sign, sizeof(can_sign));
FillAttr(&private_key_template[private_key_n_attr++], CKA_UNWRAP,
&can_unwrap, sizeof(can_unwrap));
FillStringAttr(&private_key_template[private_key_n_attr++], CKA_LABEL,
label);
FillAttr(&private_key_template[private_key_n_attr++], CKA_ID,
idstr, idstrlen);
CK_MECHANISM mechanism = {CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0};
CK_OBJECT_HANDLE public_key_handle;
CK_OBJECT_HANDLE private_key_handle;
CK_RV rv = C_GenerateKeyPair(session.handle(), &mechanism,
public_key_template, public_key_n_attr,
private_key_template, private_key_n_attr,
&public_key_handle, &private_key_handle);
if (rv != CKR_OK) {
LOG(ERROR) << "C_GenerateKeyPair() failed. rv=" << rv;
return false;
}
return true;
}
#endif
} // namespace entd