| // 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 |