| // Copyright (c) 2012 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. |
| |
| // The expensive PKCS #11 operations that occur during a VPN connect are C_Login |
| // and C_Sign. This program replays these along with minimal overhead calls. |
| // The --generate switch can be used to prepare a private key to test against. |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/command_line.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/time/time.h> |
| #include <brillo/file_utils.h> |
| #include <brillo/syslog_logging.h> |
| #include <crypto/libcrypto-compat.h> |
| #include <crypto/scoped_openssl_types.h> |
| #include <openssl/rsa.h> |
| #include <openssl/x509.h> |
| |
| #include "chaps/chaps_utility.h" |
| #include "pkcs11/cryptoki.h" |
| |
| using base::TimeDelta; |
| using base::TimeTicks; |
| using chaps::ConvertStringToByteBuffer; |
| using std::string; |
| using std::unordered_map; |
| using std::vector; |
| using ScopedPKCS8_PRIV_KEY_INFO = |
| crypto::ScopedOpenSSL<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>; |
| using ScopedASN1_OCTET_STRING = |
| crypto::ScopedOpenSSL<ASN1_OCTET_STRING, ASN1_OCTET_STRING_free>; |
| using ScopedX509 = crypto::ScopedOpenSSL<X509, X509_free>; |
| |
| namespace { |
| const char* kKeyID = "test"; |
| |
| typedef enum { |
| kPrivateKey, |
| kPublicKey, |
| kCertificate, |
| } CryptoObjectType; |
| |
| // Initializes the library and finds an appropriate slot. |
| CK_SLOT_ID Initialize() { |
| CK_RV result = C_Initialize(NULL); |
| LOG(INFO) << "C_Initialize: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| |
| CK_SLOT_ID slot_list[10]; |
| CK_ULONG slot_count = std::size(slot_list); |
| result = C_GetSlotList(CK_TRUE, slot_list, &slot_count); |
| LOG(INFO) << "C_GetSlotList: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| if (slot_count == 0) { |
| LOG(INFO) << "No slots."; |
| exit(-1); |
| } |
| return slot_list[0]; |
| } |
| |
| // Opens a session on the given slot. |
| CK_SESSION_HANDLE OpenSession(CK_SLOT_ID slot) { |
| CK_SESSION_HANDLE session = CK_INVALID_HANDLE; |
| CK_RV result = C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, |
| NULL, // Ignore callbacks. |
| NULL, // Ignore callbacks. |
| &session); |
| LOG(INFO) << "C_OpenSession: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| return session; |
| } |
| |
| // Opens a new session and performs a login. If force_login is set to true and |
| // the token is already logged in, it will be logged out and logged in again. In |
| // this case, the session will also be closed and reopened. In any case, the |
| // current, valid session is returned. |
| CK_SESSION_HANDLE Login(CK_SLOT_ID slot, |
| bool force_login, |
| CK_SESSION_HANDLE session) { |
| CK_RV result = CKR_OK; |
| bool try_again = true; |
| while (try_again) { |
| try_again = false; |
| result = C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR) "111111", 6); |
| LOG(INFO) << "C_Login: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK && result != CKR_USER_ALREADY_LOGGED_IN) |
| exit(-1); |
| if (result == CKR_USER_ALREADY_LOGGED_IN && force_login) { |
| try_again = true; |
| result = C_Logout(session); |
| LOG(INFO) << "C_Logout: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| result = C_CloseAllSessions(slot); |
| LOG(INFO) << "C_CloseAllSessions: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| session = OpenSession(slot); |
| } |
| } |
| return session; |
| } |
| |
| // Finds all objects matching the given attributes. |
| void Find(CK_SESSION_HANDLE session, |
| CK_ATTRIBUTE attributes[], |
| CK_ULONG num_attributes, |
| vector<CK_OBJECT_HANDLE>* objects) { |
| CK_RV result = C_FindObjectsInit(session, attributes, num_attributes); |
| LOG(INFO) << "C_FindObjectsInit: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| CK_OBJECT_HANDLE object = 0; |
| CK_ULONG object_count = 1; |
| while (object_count > 0) { |
| result = C_FindObjects(session, &object, 1, &object_count); |
| LOG(INFO) << "C_FindObjects: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| if (object_count > 0) { |
| objects->push_back(object); |
| } |
| } |
| result = C_FindObjectsFinal(session); |
| LOG(INFO) << "C_FindObjectsFinal: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| |
| // Sign some data with a private key. |
| void Sign(CK_SESSION_HANDLE session, const string& label) { |
| CK_OBJECT_CLASS class_value = CKO_PRIVATE_KEY; |
| CK_ATTRIBUTE attributes[] = { |
| {CKA_CLASS, &class_value, sizeof(class_value)}, |
| {CKA_ID, const_cast<char*>(kKeyID), strlen(kKeyID)}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| }; |
| vector<CK_OBJECT_HANDLE> objects; |
| Find(session, attributes, std::size(attributes), &objects); |
| if (objects.size() == 0) { |
| LOG(INFO) << "No key."; |
| exit(-1); |
| } |
| |
| CK_MECHANISM mechanism; |
| mechanism.mechanism = CKM_SHA1_RSA_PKCS; |
| mechanism.pParameter = NULL; |
| mechanism.ulParameterLen = 0; |
| CK_RV result = C_SignInit(session, &mechanism, objects[0]); |
| LOG(INFO) << "C_SignInit: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| |
| CK_BYTE data[200] = {0}; |
| CK_BYTE signature[2048] = {0}; |
| CK_ULONG signature_length = std::size(signature); |
| result = C_Sign(session, data, std::size(data), signature, &signature_length); |
| LOG(INFO) << "C_Sign: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| |
| // Generates a test key pair. |
| void GenerateKeyPair(CK_SESSION_HANDLE session, |
| int key_size_bits, |
| const string& label, |
| bool is_temp) { |
| CK_MECHANISM mechanism; |
| mechanism.mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; |
| mechanism.pParameter = NULL; |
| mechanism.ulParameterLen = 0; |
| CK_ULONG bits = key_size_bits; |
| CK_BYTE e[] = {1, 0, 1}; |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BBOOL true_value = CK_TRUE; |
| CK_ATTRIBUTE public_attributes[] = { |
| {CKA_ENCRYPT, &true_value, sizeof(true_value)}, |
| {CKA_VERIFY, &true_value, sizeof(true_value)}, |
| {CKA_WRAP, &false_value, sizeof(false_value)}, |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &false_value, sizeof(false_value)}, |
| {CKA_MODULUS_BITS, &bits, sizeof(bits)}, |
| {CKA_PUBLIC_EXPONENT, e, sizeof(e)}, |
| {CKA_ID, const_cast<char*>(kKeyID), strlen(kKeyID)}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| }; |
| CK_ATTRIBUTE private_attributes[] = { |
| {CKA_DECRYPT, &true_value, sizeof(true_value)}, |
| {CKA_SIGN, &true_value, sizeof(true_value)}, |
| {CKA_UNWRAP, &false_value, sizeof(false_value)}, |
| {CKA_SENSITIVE, &true_value, sizeof(true_value)}, |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &true_value, sizeof(true_value)}, |
| {CKA_ID, const_cast<char*>(kKeyID), strlen(kKeyID)}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| }; |
| CK_OBJECT_HANDLE public_key_handle = 0; |
| CK_OBJECT_HANDLE private_key_handle = 0; |
| CK_RV result = C_GenerateKeyPair( |
| session, &mechanism, public_attributes, std::size(public_attributes), |
| private_attributes, std::size(private_attributes), &public_key_handle, |
| &private_key_handle); |
| LOG(INFO) << "C_GenerateKeyPair: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| if (is_temp) { |
| result = C_DestroyObject(session, public_key_handle); |
| LOG(INFO) << "C_DestroyObject: " << chaps::CK_RVToString(result); |
| result = C_DestroyObject(session, private_key_handle); |
| LOG(INFO) << "C_DestroyObject: " << chaps::CK_RVToString(result); |
| } |
| } |
| |
| void DestroyKeyPair(CK_SESSION_HANDLE session, const string& label) { |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BBOOL true_value = CK_TRUE; |
| CK_ATTRIBUTE public_attributes[] = { |
| {CKA_PRIVATE, &false_value, sizeof(false_value)}, |
| {CKA_ID, const_cast<char*>(kKeyID), strlen(kKeyID)}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| }; |
| CK_ATTRIBUTE private_attributes[] = { |
| {CKA_PRIVATE, &true_value, sizeof(true_value)}, |
| {CKA_ID, const_cast<char*>(kKeyID), strlen(kKeyID)}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| }; |
| vector<CK_OBJECT_HANDLE> public_objects; |
| Find(session, public_attributes, std::size(public_attributes), |
| &public_objects); |
| vector<CK_OBJECT_HANDLE> private_objects; |
| Find(session, private_attributes, std::size(private_attributes), |
| &private_objects); |
| if (public_objects.size() == 0 && private_objects.size() == 0) { |
| LOG(INFO) << "No keypair."; |
| exit(-1); |
| } |
| for (size_t i = 0; i < public_objects.size(); ++i) { |
| CK_RV result = C_DestroyObject(session, public_objects[i]); |
| LOG(INFO) << "C_DestroyObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| for (size_t i = 0; i < private_objects.size(); ++i) { |
| CK_RV result = C_DestroyObject(session, private_objects[i]); |
| LOG(INFO) << "C_DestroyObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| } |
| |
| // TODO(crbug/916023): use shared helper after isolate the OpenSSL functions |
| // from session_impl.c |
| string bn2bin(const BIGNUM* bn) { |
| string bin; |
| bin.resize(BN_num_bytes(bn)); |
| bin.resize(BN_bn2bin(bn, ConvertStringToByteBuffer(bin.data()))); |
| return bin; |
| } |
| |
| string name2bin(X509_NAME* name) { |
| string bin; |
| bin.resize(i2d_X509_NAME(name, NULL)); |
| uint8_t* data_start = ConvertStringToByteBuffer(bin.data()); |
| i2d_X509_NAME(name, &data_start); |
| return bin; |
| } |
| |
| string asn1integer2bin(ASN1_INTEGER* serial_number) { |
| string bin; |
| bin.resize(i2d_ASN1_INTEGER(serial_number, NULL)); |
| uint8_t* data_start = ConvertStringToByteBuffer(bin.data()); |
| i2d_ASN1_INTEGER(serial_number, &data_start); |
| return bin; |
| } |
| |
| template <typename OpenSSLType, |
| int (*openssl_func)(OpenSSLType*, unsigned char**)> |
| string ConvertOpenSSLObjectToString(OpenSSLType* object) { |
| string output; |
| |
| int expected_size = openssl_func(object, nullptr); |
| if (expected_size < 0) |
| return string(); |
| |
| output.resize(expected_size, '\0'); |
| |
| unsigned char* buf = ConvertStringToByteBuffer(output.data()); |
| int real_size = openssl_func(object, &buf); |
| CHECK_EQ(expected_size, real_size); |
| |
| return output; |
| } |
| |
| string ecparameters2bin(EC_KEY* key) { |
| string bin; |
| bin.resize(i2d_ECParameters(key, nullptr)); |
| uint8_t* data_start = ConvertStringToByteBuffer(bin.data()); |
| i2d_ECParameters(key, &data_start); |
| return bin; |
| } |
| |
| string ecpoint2bin(EC_KEY* key) { |
| // Convert EC_KEY* to OCT_STRING |
| const string oct_string = |
| ConvertOpenSSLObjectToString<EC_KEY, chaps::i2o_ECPublicKey_nc>(key); |
| |
| // Put OCT_STRING to ASN1_OCTET_STRING |
| ScopedASN1_OCTET_STRING os(ASN1_OCTET_STRING_new()); |
| ASN1_OCTET_STRING_set(os.get(), ConvertStringToByteBuffer(oct_string.data()), |
| oct_string.size()); |
| |
| // DER encode ASN1_OCTET_STRING |
| const string der_encoded = |
| ConvertOpenSSLObjectToString<ASN1_OCTET_STRING, i2d_ASN1_OCTET_STRING>( |
| os.get()); |
| |
| return der_encoded; |
| } |
| |
| void CreateRSAPrivateKey(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| string label, |
| RSA* rsa, |
| bool force_software) { |
| CK_OBJECT_CLASS priv_class = CKO_PRIVATE_KEY; |
| CK_KEY_TYPE key_type = CKK_RSA; |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BBOOL true_value = CK_TRUE; |
| const BIGNUM* rsa_n; |
| const BIGNUM* rsa_e; |
| const BIGNUM* rsa_d; |
| const BIGNUM* rsa_p; |
| const BIGNUM* rsa_q; |
| const BIGNUM* rsa_dmp1; |
| const BIGNUM* rsa_dmq1; |
| const BIGNUM* rsa_iqmp; |
| RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d); |
| RSA_get0_factors(rsa, &rsa_p, &rsa_q); |
| RSA_get0_crt_params(rsa, &rsa_dmp1, &rsa_dmq1, &rsa_iqmp); |
| string n = bn2bin(rsa_n); |
| string e = bn2bin(rsa_e); |
| string d = bn2bin(rsa_d); |
| string p = bn2bin(rsa_p); |
| string q = bn2bin(rsa_q); |
| string dmp1 = bn2bin(rsa_dmp1); |
| string dmq1 = bn2bin(rsa_dmq1); |
| string iqmp = bn2bin(rsa_iqmp); |
| CK_BBOOL force_software_value = force_software ? CK_TRUE : CK_FALSE; |
| |
| CK_ATTRIBUTE private_attributes[] = { |
| {CKA_CLASS, &priv_class, sizeof(priv_class)}, |
| {CKA_KEY_TYPE, &key_type, sizeof(key_type)}, |
| {CKA_DECRYPT, &true_value, sizeof(true_value)}, |
| {CKA_SIGN, &true_value, sizeof(true_value)}, |
| {CKA_UNWRAP, &false_value, sizeof(false_value)}, |
| {CKA_SENSITIVE, &true_value, sizeof(true_value)}, |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &true_value, sizeof(true_value)}, |
| {chaps::kForceSoftwareAttribute, &force_software_value, |
| sizeof(force_software_value)}, |
| {CKA_ID, const_cast<uint8_t*>(object_id.data()), object_id.size()}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| {CKA_MODULUS, const_cast<char*>(n.c_str()), n.length()}, |
| {CKA_PUBLIC_EXPONENT, const_cast<char*>(e.c_str()), e.length()}, |
| {CKA_PRIVATE_EXPONENT, const_cast<char*>(d.c_str()), d.length()}, |
| {CKA_PRIME_1, const_cast<char*>(p.c_str()), p.length()}, |
| {CKA_PRIME_2, const_cast<char*>(q.c_str()), q.length()}, |
| {CKA_EXPONENT_1, const_cast<char*>(dmp1.c_str()), dmp1.length()}, |
| {CKA_EXPONENT_2, const_cast<char*>(dmq1.c_str()), dmq1.length()}, |
| {CKA_COEFFICIENT, const_cast<char*>(iqmp.c_str()), iqmp.length()}, |
| }; |
| CK_OBJECT_HANDLE private_key_handle = 0; |
| CK_RV result = |
| C_CreateObject(session, private_attributes, std::size(private_attributes), |
| &private_key_handle); |
| LOG(INFO) << "C_CreateObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) { |
| exit(-1); |
| } |
| } |
| |
| void CreateRSAPublicKey(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| const string label, |
| int key_size_bits, |
| RSA* rsa) { |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BBOOL true_value = CK_TRUE; |
| CK_OBJECT_CLASS pub_class = CKO_PUBLIC_KEY; |
| CK_KEY_TYPE key_type = CKK_RSA; |
| CK_ULONG bits = key_size_bits; |
| const BIGNUM* rsa_n; |
| const BIGNUM* rsa_e; |
| RSA_get0_key(rsa, &rsa_n, &rsa_e, nullptr); |
| string n = bn2bin(rsa_n); |
| string e = bn2bin(rsa_e); |
| CK_ATTRIBUTE public_attributes[] = { |
| {CKA_CLASS, &pub_class, sizeof(pub_class)}, |
| {CKA_KEY_TYPE, &key_type, sizeof(key_type)}, |
| {CKA_ENCRYPT, &true_value, sizeof(true_value)}, |
| {CKA_VERIFY, &true_value, sizeof(true_value)}, |
| {CKA_WRAP, &false_value, sizeof(false_value)}, |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &false_value, sizeof(false_value)}, |
| {CKA_ID, const_cast<uint8_t*>(object_id.data()), object_id.size()}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| {CKA_MODULUS_BITS, &bits, sizeof(bits)}, |
| {CKA_MODULUS, const_cast<char*>(n.c_str()), n.length()}, |
| {CKA_PUBLIC_EXPONENT, const_cast<char*>(e.c_str()), e.length()}, |
| }; |
| CK_OBJECT_HANDLE public_key_handle = 0; |
| CK_RV result = |
| C_CreateObject(session, public_attributes, std::size(public_attributes), |
| &public_key_handle); |
| LOG(INFO) << "C_CreateObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| |
| void CreateECCPublicKey(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| string label, |
| EC_KEY* ecc) { |
| CK_OBJECT_CLASS pub_class = CKO_PUBLIC_KEY; |
| CK_KEY_TYPE key_type = CKK_EC; |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BBOOL true_value = CK_TRUE; |
| |
| string params = ecparameters2bin(ecc); |
| string point = ecpoint2bin(ecc); |
| |
| CK_ATTRIBUTE public_attributes[] = { |
| {CKA_CLASS, &pub_class, sizeof(pub_class)}, |
| {CKA_KEY_TYPE, &key_type, sizeof(key_type)}, |
| {CKA_ENCRYPT, &true_value, sizeof(true_value)}, |
| {CKA_VERIFY, &true_value, sizeof(true_value)}, |
| {CKA_WRAP, &false_value, sizeof(false_value)}, |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &false_value, sizeof(false_value)}, |
| {CKA_ID, const_cast<uint8_t*>(object_id.data()), object_id.size()}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| {CKA_EC_PARAMS, const_cast<char*>(params.c_str()), params.length()}, |
| {CKA_EC_POINT, const_cast<char*>(point.c_str()), point.length()}, |
| }; |
| CK_OBJECT_HANDLE public_key_handle = 0; |
| CK_RV result = |
| C_CreateObject(session, public_attributes, std::size(public_attributes), |
| &public_key_handle); |
| LOG(INFO) << "C_CreateObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) { |
| exit(-1); |
| } |
| } |
| |
| void CreateECCPrivateKey(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| string label, |
| EC_KEY* ecc, |
| bool force_software) { |
| CK_OBJECT_CLASS priv_class = CKO_PRIVATE_KEY; |
| CK_KEY_TYPE key_type = CKK_EC; |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BBOOL true_value = CK_TRUE; |
| CK_BBOOL force_software_value = force_software ? CK_TRUE : CK_FALSE; |
| |
| string d = bn2bin(EC_KEY_get0_private_key(ecc)); |
| string params = ecparameters2bin(ecc); |
| |
| CK_ATTRIBUTE private_attributes[] = { |
| {CKA_CLASS, &priv_class, sizeof(priv_class)}, |
| {CKA_KEY_TYPE, &key_type, sizeof(key_type)}, |
| {CKA_DECRYPT, &true_value, sizeof(true_value)}, |
| {CKA_SIGN, &true_value, sizeof(true_value)}, |
| {CKA_UNWRAP, &false_value, sizeof(false_value)}, |
| {CKA_SENSITIVE, &true_value, sizeof(true_value)}, |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &true_value, sizeof(true_value)}, |
| {chaps::kForceSoftwareAttribute, &force_software_value, |
| sizeof(force_software_value)}, |
| {CKA_ID, const_cast<uint8_t*>(object_id.data()), object_id.size()}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| {CKA_EC_PARAMS, const_cast<char*>(params.c_str()), params.length()}, |
| {CKA_VALUE, const_cast<char*>(d.c_str()), d.length()}, |
| }; |
| CK_OBJECT_HANDLE private_key_handle = 0; |
| CK_RV result = |
| C_CreateObject(session, private_attributes, std::size(private_attributes), |
| &private_key_handle); |
| LOG(INFO) << "C_CreateObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) { |
| exit(-1); |
| } |
| } |
| |
| void CreateCertificate(CK_SESSION_HANDLE session, |
| const string& value, |
| const vector<uint8_t>& object_id, |
| X509* cert) { |
| string subject = name2bin(X509_get_subject_name(cert)); |
| string issuer = name2bin(X509_get_issuer_name(cert)); |
| string serial = asn1integer2bin(X509_get_serialNumber(cert)); |
| string label = "testing_cert"; |
| CK_OBJECT_CLASS clazz = CKO_CERTIFICATE; |
| CK_CERTIFICATE_TYPE cert_type = CKC_X_509; |
| CK_BBOOL is_true = CK_TRUE; |
| CK_ATTRIBUTE attributes[] = { |
| {CKA_CLASS, &clazz, sizeof(clazz)}, |
| {CKA_CERTIFICATE_TYPE, &cert_type, sizeof(cert_type)}, |
| {CKA_TOKEN, &is_true, sizeof(is_true)}, |
| {CKA_VALUE, const_cast<char*>(value.c_str()), value.length()}, |
| {CKA_ID, const_cast<uint8_t*>(object_id.data()), object_id.size()}, |
| {CKA_SUBJECT, const_cast<char*>(subject.c_str()), subject.length()}, |
| {CKA_ISSUER, const_cast<char*>(issuer.c_str()), issuer.length()}, |
| {CKA_SERIAL_NUMBER, const_cast<char*>(serial.c_str()), serial.length()}, |
| {CKA_LABEL, const_cast<char*>(label.c_str()), label.length()}, |
| }; |
| CK_OBJECT_HANDLE handle = 0; |
| CK_RV result = |
| C_CreateObject(session, attributes, std::size(attributes), &handle); |
| LOG(INFO) << "C_CreateObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| |
| crypto::ScopedRSA ParseRSAPublicKey(const std::string& object_data) { |
| // Try decoding a PKCS#1 RSAPublicKey structure. |
| const unsigned char* buf = ConvertStringToByteBuffer(object_data.data()); |
| crypto::ScopedRSA rsa(d2i_RSAPublicKey(NULL, &buf, object_data.length())); |
| if (rsa != nullptr) { |
| LOG(INFO) << "Recognized as PKCS#1 RSA RSAPublicKey."; |
| return rsa; |
| } |
| |
| // Try decoding a X.509 SubjectPublicKeyInfo structure. |
| // Rewind the ptr just in case it was modified. |
| buf = ConvertStringToByteBuffer(object_data.data()); |
| rsa.reset(d2i_RSA_PUBKEY(NULL, &buf, object_data.length())); |
| if (rsa != nullptr) { |
| LOG(INFO) << "Recognized as X.509 SubjectPublicKeyInfo RSA PUBKEY."; |
| return rsa; |
| } |
| |
| return nullptr; |
| } |
| |
| crypto::ScopedRSA ParseRSAPrivateKey(const std::string& object_data) { |
| // Try decoding a PKCS#1 RSAPrivateKey structure. |
| const unsigned char* buf = ConvertStringToByteBuffer(object_data.data()); |
| crypto::ScopedRSA rsa(d2i_RSAPrivateKey(nullptr, &buf, object_data.length())); |
| if (rsa != nullptr) { |
| LOG(INFO) << "Recognized as PKCS#1 RSA private key"; |
| return rsa; |
| } |
| |
| // Try decoding a PKCS#8 structure. |
| // Rewind the ptr just in case it was modified. |
| buf = ConvertStringToByteBuffer(object_data.data()); |
| ScopedPKCS8_PRIV_KEY_INFO p8( |
| d2i_PKCS8_PRIV_KEY_INFO(nullptr, &buf, object_data.length())); |
| if (p8 == nullptr) |
| return nullptr; |
| |
| crypto::ScopedEVP_PKEY pkey(EVP_PKCS82PKEY(p8.get())); |
| // See if we have a RSAPrivateKey in the PKCS#8 structure. |
| if (pkey == nullptr || EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) |
| return nullptr; |
| |
| rsa.reset(EVP_PKEY_get0_RSA(pkey.get())); |
| if (rsa == nullptr) |
| return nullptr; |
| |
| LOG(INFO) << "Recognized as PKCS#8 RSA private key"; |
| return rsa; |
| } |
| |
| crypto::ScopedEC_KEY ParseECCPublicKey(const std::string& object_data) { |
| crypto::ScopedEC_KEY ecc; |
| |
| // Try decoding a X.509 SubjectPublicKeyInfo structure. |
| const unsigned char* data_start = |
| reinterpret_cast<const unsigned char*>(object_data.c_str()); |
| ecc.reset(d2i_EC_PUBKEY(NULL, &data_start, object_data.size())); |
| if (ecc != nullptr) { |
| LOG(INFO) << "Recognized as X.509 SubjectPublicKeyInfo EC PUBKEY"; |
| return ecc; |
| } |
| |
| return nullptr; |
| } |
| |
| crypto::ScopedEC_KEY ParseECCPrivateKey(const std::string& object_data) { |
| crypto::ScopedEC_KEY ecc; |
| |
| // Try decoding a RFC 5915 ECPrivateKey structure. |
| const unsigned char* data_start = |
| reinterpret_cast<const unsigned char*>(object_data.c_str()); |
| ecc.reset(d2i_ECPrivateKey(NULL, &data_start, object_data.size())); |
| if (ecc != nullptr) { |
| LOG(INFO) << "Recognized as RFC 5915 ECPrivateKey"; |
| return ecc; |
| } |
| |
| return nullptr; |
| } |
| |
| bool ParseAndCreatePublicKey(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| const string& object_data) { |
| // Try RSA |
| crypto::ScopedRSA rsa = ParseRSAPublicKey(object_data); |
| if (rsa != nullptr) { |
| int key_size_bits = RSA_size(rsa.get()) * 8; |
| // Round the key up to the nearest 256 bit boundary. |
| key_size_bits = (key_size_bits / 256 + 1) * 256; |
| |
| CreateRSAPublicKey(session, object_id, "testing_key", key_size_bits, |
| rsa.get()); |
| return true; |
| } |
| |
| // Try ECC |
| crypto::ScopedEC_KEY ecc = ParseECCPublicKey(object_data); |
| if (ecc != nullptr) { |
| CreateECCPublicKey(session, object_id, "testing_key", ecc.get()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ParseAndCreatePrivateKey(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| const string& object_data, |
| bool force_software) { |
| // Try RSA |
| crypto::ScopedRSA rsa = ParseRSAPrivateKey(object_data); |
| if (rsa != nullptr) { |
| CreateRSAPrivateKey(session, object_id, "testing_key", rsa.get(), |
| force_software); |
| return true; |
| } |
| |
| // Try ECC |
| crypto::ScopedEC_KEY ecc = ParseECCPrivateKey(object_data); |
| if (ecc != nullptr) { |
| CreateECCPrivateKey(session, object_id, "testing_key", ecc.get(), |
| force_software); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ParseAndCreateCertificate(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| const string& object_data) { |
| const unsigned char* buf = ConvertStringToByteBuffer(object_data.data()); |
| ScopedX509 certificate(d2i_X509(NULL, &buf, object_data.length())); |
| if (certificate == nullptr) |
| return false; |
| CreateCertificate(session, object_data, object_id, certificate.get()); |
| return true; |
| } |
| |
| void ReadInObject(CK_SESSION_HANDLE session, |
| const string& input_path, |
| const vector<uint8_t>& object_id, |
| CryptoObjectType type, |
| bool force_software) { |
| const base::FilePath path(input_path); |
| string object_data; |
| if (!base::ReadFileToString(path, &object_data)) { |
| LOG(ERROR) << "Failed to read object from file."; |
| exit(-1); |
| } |
| |
| string type_str; |
| bool result = false; |
| switch (type) { |
| case kCertificate: |
| result = ParseAndCreateCertificate(session, object_id, object_data); |
| type_str = "Certificate"; |
| break; |
| case kPublicKey: |
| result = ParseAndCreatePublicKey(session, object_id, object_data); |
| type_str = "Public key"; |
| break; |
| case kPrivateKey: |
| result = ParseAndCreatePrivateKey(session, object_id, object_data, |
| force_software); |
| type_str = "Private key"; |
| break; |
| } |
| |
| if (!result) { |
| LOG(ERROR) << __func__ << ": " << type_str << " parsing fail."; |
| exit(-1); |
| } |
| } |
| |
| // Generates a test key pair locally and injects it. |
| void InjectRSAKeyPair(CK_SESSION_HANDLE session, |
| int key_size_bits, |
| const string& label) { |
| crypto::ScopedRSA rsa(RSA_new()); |
| crypto::ScopedBIGNUM e(BN_new()); |
| if (!rsa || !e) { |
| LOG(ERROR) << "Failed to allocate RSA or exponent for key pair."; |
| exit(-1); |
| } |
| if (!BN_set_word(e.get(), 0x10001) || |
| !RSA_generate_key_ex(rsa.get(), key_size_bits, e.get(), nullptr)) { |
| LOG(ERROR) << "Failed to locally generate key pair."; |
| exit(-1); |
| } |
| vector<uint8_t> id(kKeyID, kKeyID + strlen(kKeyID)); |
| CreateRSAPublicKey(session, id, label, key_size_bits, rsa.get()); |
| CreateRSAPrivateKey(session, id, label, rsa.get(), false); |
| } |
| |
| // Deletes all test keys previously created. |
| void DeleteAllTestKeys(CK_SESSION_HANDLE session) { |
| CK_OBJECT_CLASS class_value = CKO_PRIVATE_KEY; |
| CK_ATTRIBUTE attributes[] = { |
| {CKA_CLASS, &class_value, sizeof(class_value)}, |
| {CKA_ID, const_cast<char*>(kKeyID), strlen(kKeyID)}}; |
| vector<CK_OBJECT_HANDLE> objects; |
| Find(session, attributes, std::size(attributes), &objects); |
| class_value = CKO_PUBLIC_KEY; |
| Find(session, attributes, std::size(attributes), &objects); |
| for (size_t i = 0; i < objects.size(); ++i) { |
| CK_RV result = C_DestroyObject(session, objects[i]); |
| LOG(INFO) << "C_DestroyObject: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) |
| exit(-1); |
| } |
| } |
| |
| // Retrieve the object handle for the object with the specified |object_id| and |
| // CKA_CLASS of |obj_type|, and return the object iff exactly one object is |
| // found. Exit with a non-zero status code otherwise. |
| CK_OBJECT_HANDLE GetObjectOrDie(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| string obj_type) { |
| CK_OBJECT_CLASS class_value; |
| if (base::EqualsCaseInsensitiveASCII(obj_type, "privkey")) { |
| class_value = CKO_PRIVATE_KEY; |
| } else if (base::EqualsCaseInsensitiveASCII(obj_type, "pubkey")) { |
| class_value = CKO_PUBLIC_KEY; |
| } else if (base::EqualsCaseInsensitiveASCII(obj_type, "cert")) { |
| class_value = CKO_CERTIFICATE; |
| } else { |
| LOG(INFO) << "Invalid object class: " << obj_type; |
| exit(-1); |
| } |
| |
| CK_ATTRIBUTE attributes[] = { |
| {CKA_CLASS, &class_value, sizeof(class_value)}, |
| {CKA_ID, |
| const_cast<char*>(reinterpret_cast<const char*>(object_id.data())), |
| object_id.size()}, |
| }; |
| vector<CK_OBJECT_HANDLE> objects; |
| Find(session, attributes, std::size(attributes), &objects); |
| if (objects.size() == 0) { |
| LOG(INFO) << "No object found."; |
| exit(-1); |
| } |
| if (objects.size() > 1) { |
| LOG(INFO) << "More than 1 object."; |
| exit(-1); |
| } |
| return objects[0]; |
| } |
| |
| // Get the specified attribute for the specified object and print it out in |
| // specified format. |
| void GetAttribute(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| CK_ATTRIBUTE_TYPE attribute, |
| string output_format, |
| string obj_type) { |
| CK_OBJECT_HANDLE object = GetObjectOrDie(session, object_id, obj_type); |
| |
| // Get the length of the attribute. |
| CK_ATTRIBUTE attribute_template[] = { |
| {attribute, nullptr, 0}, |
| }; |
| CK_RV ret = C_GetAttributeValue(session, object, attribute_template, |
| std::size(attribute_template)); |
| if (ret != CKR_OK) { |
| printf("Unable to access the attribute, error: %s\n", |
| chaps::CK_RVToString(ret)); |
| exit(-1); |
| } |
| if (attribute_template[0].ulValueLen == -1) { |
| printf("Unable to access the attribute, got -1 for attribute length.\n"); |
| exit(-1); |
| } |
| |
| size_t attribute_size = attribute_template[0].ulValueLen; |
| printf("Size: %d\n", static_cast<int>(attribute_size)); |
| |
| if (attribute_size <= 0) { |
| // No data, we are done here. |
| return; |
| } |
| |
| // Get the object value. |
| std::vector<uint8_t> buffer(attribute_template[0].ulValueLen, 0); |
| attribute_template[0].pValue = std::data(buffer); |
| ret = C_GetAttributeValue(session, object, attribute_template, |
| std::size(attribute_template)); |
| if (ret != CKR_OK) { |
| printf("Unable to read the attribute, error: %s\n", |
| chaps::CK_RVToString(ret)); |
| exit(-1); |
| } |
| |
| // Print out the attribute value. |
| if (output_format == "hex" || output_format == "") { |
| printf("Attribute Data in hex: %s\n", |
| base::HexEncode(std::data(buffer), buffer.size()).c_str()); |
| } else { |
| printf("Invalid output format: %s\n", output_format.c_str()); |
| exit(-1); |
| } |
| } |
| |
| // Set the specified attribute for the specified object. |
| void SetAttribute(CK_SESSION_HANDLE session, |
| const vector<uint8_t>& object_id, |
| CK_ATTRIBUTE_TYPE attribute, |
| string obj_type, |
| const vector<uint8_t>& data_to_write) { |
| CK_OBJECT_HANDLE object = GetObjectOrDie(session, object_id, obj_type); |
| |
| // Set the attribute |
| // Cryptoki wants a non-const buffer in template. |
| vector<uint8_t> buffer = data_to_write; |
| CK_ATTRIBUTE attribute_template[] = { |
| {attribute, std::data(buffer), buffer.size()}, |
| }; |
| CK_RV ret = C_SetAttributeValue(session, object, attribute_template, |
| std::size(attribute_template)); |
| if (ret != CKR_OK) { |
| printf("Failed to set attribute, error: %s\n", chaps::CK_RVToString(ret)); |
| exit(-1); |
| } |
| printf("Set attribute OK.\n"); |
| } |
| |
| void CopyObject( |
| CK_SESSION_HANDLE session, |
| string obj_type, |
| const vector<uint8_t>& object_id, |
| const unordered_map<CK_ATTRIBUTE_TYPE, vector<uint8_t>>& attr_map) { |
| CK_OBJECT_HANDLE object = GetObjectOrDie(session, object_id, obj_type); |
| |
| vector<CK_ATTRIBUTE> copy_template{attr_map.size()}; |
| // This will hold the buffer that is pointed to by copy_template's pValue. |
| vector<vector<uint8_t>> value_holder; |
| int i = 0; |
| for (const auto& itr : attr_map) { |
| CHECK(i < copy_template.size()); |
| copy_template[i].type = itr.first; |
| |
| // Copy the value buffer into value_holder. |
| value_holder.push_back(itr.second); |
| auto& buffer = value_holder[value_holder.size() - 1]; |
| |
| copy_template[i].pValue = reinterpret_cast<CK_VOID_PTR>(std::data(buffer)); |
| copy_template[i].ulValueLen = buffer.size(); |
| i++; |
| } |
| |
| CK_OBJECT_HANDLE new_object; |
| CK_RV result = |
| C_CopyObject(session, object, |
| reinterpret_cast<CK_ATTRIBUTE_PTR>(std::data(copy_template)), |
| copy_template.size(), &new_object); |
| if (result != CKR_OK) { |
| printf("Failed to copy the attribute, error: %s\n", |
| chaps::CK_RVToString(result)); |
| exit(-1); |
| } |
| printf("Operation completed successfully.\n"); |
| } |
| |
| // This checks if a session is still functional/open. |
| bool TestSession(CK_SESSION_HANDLE session) { |
| CK_OBJECT_CLASS class_value = CKO_PUBLIC_KEY; |
| CK_ATTRIBUTE attributes[] = { |
| {CKA_CLASS, &class_value, sizeof(class_value)}, |
| }; |
| |
| CK_RV result = C_FindObjectsInit(session, attributes, std::size(attributes)); |
| LOG(INFO) << "C_FindObjectsInit: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) { |
| return false; |
| } |
| |
| CK_OBJECT_HANDLE object = 0; |
| CK_ULONG object_count = 1; |
| result = C_FindObjects(session, &object, 1, &object_count); |
| LOG(INFO) << "C_FindObjects: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) { |
| return false; |
| } |
| |
| result = C_FindObjectsFinal(session); |
| LOG(INFO) << "C_FindObjectsFinal: " << chaps::CK_RVToString(result); |
| if (result != CKR_OK) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int ReplayCloseAllSessionsLoop(CK_SESSION_HANDLE session, |
| const std::string& ipc_file_path) { |
| // Check session is operation at the start. |
| bool success = TestSession(session); |
| if (!success) { |
| LOG(ERROR) |
| << "Session destroyed at the start of ReplayCloseAllSessionsLoop()"; |
| return 1; |
| } |
| |
| // Since we have an operation session, touch the file to let |
| // ReplayCloseAllSessionsCheck() know. |
| base::FilePath path(ipc_file_path); |
| CHECK(brillo::WriteStringToFile(path, "")) << "Failed to write ipc_file"; |
| CHECK(brillo::SyncFileOrDirectory( |
| path.DirName(), true /* is directory? */, |
| false /* data_sync? false because we want to sync metadata */)) |
| << "Failed to sync after writing ipc_file"; |
| |
| // We'll test that the session works during the test run time. |
| for (int i = 0; i < 30; i++) { |
| bool success = TestSession(session); |
| bool done = !base::PathExists(path); |
| if (!success) { |
| LOG(ERROR) |
| << "Session destroyed halfway during ReplayCloseAllSessionsLoop()"; |
| return 1; |
| } |
| if (done) { |
| LOG(INFO) |
| << "Signaled by ReplayCloseAllSessionsCheck() that they are done."; |
| return 0; |
| } |
| base::PlatformThread::Sleep(base::Milliseconds(300)); |
| } |
| LOG(ERROR) |
| << "Timed out waiting for signal from ReplayCloseAllSessionsCheck()."; |
| return 1; |
| } |
| |
| int ReplayCloseAllSessionsCheck(CK_SESSION_HANDLE session, |
| CK_SLOT_ID slotID, |
| const std::string& ipc_file_path) { |
| base::FilePath path(ipc_file_path); |
| |
| // Wait for ipc_file_path to exist (i.e. ReplayCloseAllSessionsLoop() is |
| // ready). |
| constexpr int kWaitLoopCount = 30; |
| for (int i = 0; i < kWaitLoopCount; i++) { |
| if (i == kWaitLoopCount - 1) { |
| LOG(ERROR) |
| << "Timed out waiting for signal from ReplayCloseAllSessionsLoop()."; |
| return 1; |
| } |
| if (base::PathExists(path)) |
| break; |
| base::PlatformThread::Sleep(base::Milliseconds(300)); |
| } |
| |
| base::ScopedClosureRunner ipc_file_cleanup(base::BindOnce( |
| [](const std::string& ipc_file_path) { |
| base::FilePath path(ipc_file_path); |
| |
| CHECK(base::DeleteFile(path)) |
| << "Failed to delete ipc_file after ReplayCloseAllSessionsCheck()"; |
| CHECK(brillo::SyncFileOrDirectory( |
| path.DirName(), true /* is directory? */, |
| false /* data_sync? false because we want to sync metadata */)) |
| << "Failed to sync after writing ipc_file"; |
| }, |
| ipc_file_path)); |
| |
| // Check session works at first, then call C_CloseAllSessions(), then check it |
| // doesn't work. |
| bool success = TestSession(session); |
| if (!success) { |
| LOG(ERROR) |
| << "Session doesn't work at the start of ReplayCloseAllSessionsCheck()"; |
| return 1; |
| } |
| |
| CK_RV rv = C_CloseAllSessions(slotID); |
| if (rv != CKR_OK) { |
| LOG(ERROR) |
| << "Failed to C_CloseAllSessions() in ReplayCloseAllSessionsCheck()"; |
| return 1; |
| } |
| |
| success = TestSession(session); |
| if (success) { |
| LOG(ERROR) << "Session still works after C_CloseAllSessions() in " |
| "ReplayCloseAllSessionsCheck()"; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| // Cleans up the session and library. |
| void TearDown(CK_SESSION_HANDLE session, bool logout) { |
| CK_RV result = CKR_OK; |
| if (logout) { |
| result = C_Logout(session); |
| LOG(INFO) << "C_Logout: " << chaps::CK_RVToString(result); |
| } |
| result = C_CloseSession(session); |
| LOG(INFO) << "C_CloseSession: " << chaps::CK_RVToString(result); |
| result = C_Finalize(NULL); |
| LOG(INFO) << "C_Finalize: " << chaps::CK_RVToString(result); |
| } |
| |
| void PrintHelp() { |
| printf("Usage: p11_replay [--slot=<slot>] [COMMAND]\n"); |
| printf("Commands:\n"); |
| printf(" --cleanup : Deletes all test keys.\n"); |
| printf( |
| " --generate [--label=<key_label> --key_size=<size_in_bits>]" |
| " : Generates a key pair suitable for replay tests.\n"); |
| printf( |
| " --generate_delete : Generates a key pair and deletes it. This is " |
| "useful for comparing key generation on different TPM models.\n"); |
| printf( |
| " --import --path=<path to file> --type=<cert, privkey, pubkey>" |
| " --id=<token id str>" |
| " : Reads an object into the token. Accepts DER formatted X.509" |
| " certificates and DER formatted PKCS#1 or PKCS#8 private keys.\n"); |
| printf( |
| " --inject [--label=<key_label> --key_size=<size_in_bits>]" |
| " : Locally generates a key pair suitable for replay tests and injects" |
| " it into the token.\n"); |
| printf(" --list_objects : Lists all token objects.\n"); |
| printf(" --list_tokens: Lists token info for each loaded token.\n"); |
| printf(" --logout : Logs out once all other commands have finished.\n"); |
| printf( |
| " --replay_vpn [--label=<key_label>]" |
| " : Replays a L2TP/IPSEC VPN negotiation.\n"); |
| printf( |
| " --replay_wifi [--label=<key_label> --skip_generate]" |
| " : Replays a EAP-TLS Wifi negotiation. This is the default command if" |
| " no command is specified. Do not generate key pair if --skip_generate" |
| " is set\n"); |
| printf( |
| " --get_attribute --id=<token id str> --type=<cert, privkey, pubkey> " |
| "--attribute=<attribute>: Get the attribute for an object.\n"); |
| printf( |
| " --set_attribute --id=<token id str> --type=<cert, privkey, pubkey> " |
| "--attribute=<attribute> --data=<raw hex string>: Set the attribute for " |
| "an object.\n"); |
| printf( |
| " --copy_object --id=<token id str> --attr_list=CKA_XXX:<hex " |
| "value>,CKA_YYY:<hex value>,... --type=<cert, privkey, pubkey>: Copy the " |
| "object specified by --id into a new object.\n"); |
| printf( |
| " --replay_close_all_sessions --check_close_all_sessions " |
| "--ipc_file=<path>: This is a helper for hwsec.ChapsCloseAllSessions " |
| "test. --check_close_all_sessions will open a session, check it works, " |
| "then close it with C_CloseAllSessions(), then check the session is " |
| "indeed closed. ipc_file should point to a file that does not yet exist, " |
| "and is the same that is passed to --use_sessions_loop.\n"); |
| printf( |
| " --replay_close_all_sessions --use_sessions_loop --ipc_file=<path>: " |
| "Same as above, but it repeately uses a session that it opened for " |
| "around 5 seconds to make sure it doesn't get invalidated for no reason. " |
| "See above for --ipc_file.\n"); |
| } |
| |
| void PrintTicks(base::TimeTicks* start_ticks) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| base::TimeDelta delta = now - *start_ticks; |
| *start_ticks = now; |
| intmax_t value = delta.InMillisecondsRoundedUp(); |
| printf("Elapsed: %jdms\n", value); |
| } |
| |
| void PrintObjects(const vector<CK_OBJECT_HANDLE>& objects) { |
| for (size_t i = 0; i < objects.size(); ++i) { |
| if (i > 0) |
| printf(", "); |
| printf("%d", static_cast<int>(objects[i])); |
| } |
| printf("\n"); |
| } |
| |
| class DigestTestThread : public base::PlatformThread::Delegate { |
| public: |
| explicit DigestTestThread(CK_SLOT_ID slot) : slot_(slot) {} |
| void ThreadMain() { |
| const int kNumIterations = 100; |
| CK_BYTE data[1024] = {0}; |
| CK_ULONG data_length = std::size(data); |
| CK_BYTE digest[32]; |
| CK_ULONG digest_length = std::size(digest); |
| CK_MECHANISM mechanism = {CKM_SHA256, NULL, 0}; |
| CK_SESSION_HANDLE session = OpenSession(slot_); |
| for (int i = 0; i < kNumIterations; ++i) { |
| TimeTicks start = TimeTicks::Now(); |
| C_DigestInit(session, &mechanism); |
| C_DigestUpdate(session, data, data_length); |
| C_DigestFinal(session, digest, &digest_length); |
| TimeDelta delta = TimeTicks::Now() - start; |
| if (delta > base::Milliseconds(500)) { |
| LOG(WARNING) << "Hash took long: " << delta.InMilliseconds(); |
| } |
| } |
| C_CloseSession(session); |
| } |
| |
| private: |
| CK_SLOT_ID slot_; |
| }; |
| |
| void PrintTokens() { |
| CK_RV result = CKR_OK; |
| CK_SLOT_ID slot_list[10]; |
| CK_ULONG slot_count = std::size(slot_list); |
| result = C_GetSlotList(CK_TRUE, slot_list, &slot_count); |
| if (result != CKR_OK) |
| exit(-1); |
| for (CK_ULONG i = 0; i < slot_count; ++i) { |
| CK_SLOT_INFO slot_info; |
| result = C_GetSlotInfo(slot_list[i], &slot_info); |
| if (result != CKR_OK) |
| exit(-1); |
| printf("Slot %d: ", static_cast<int>(slot_list[i])); |
| if (slot_info.flags & CKF_TOKEN_PRESENT) { |
| CK_TOKEN_INFO token_info; |
| result = C_GetTokenInfo(slot_list[i], &token_info); |
| if (result != CKR_OK) |
| exit(-1); |
| string label(reinterpret_cast<char*>(token_info.label), |
| std::size(token_info.label)); |
| printf("%s\n", label.c_str()); |
| } else { |
| printf("No token present.\n"); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| base::CommandLine::Init(argc, argv); |
| base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); |
| if (cl->HasSwitch("h") || cl->HasSwitch("help")) { |
| PrintHelp(); |
| return 0; |
| } |
| bool generate = cl->HasSwitch("generate"); |
| bool inject = cl->HasSwitch("inject"); |
| bool generate_delete = cl->HasSwitch("generate_delete"); |
| bool vpn = cl->HasSwitch("replay_vpn"); |
| bool wifi = cl->HasSwitch("replay_wifi") || (cl->GetSwitches().size() == 0); |
| bool logout = cl->HasSwitch("logout"); |
| bool cleanup = cl->HasSwitch("cleanup"); |
| bool list_objects = cl->HasSwitch("list_objects"); |
| bool import = cl->HasSwitch("import") && cl->HasSwitch("path") && |
| cl->HasSwitch("type") && cl->HasSwitch("id"); |
| bool digest_test = cl->HasSwitch("digest_test"); |
| bool list_tokens = cl->HasSwitch("list_tokens"); |
| bool get_attribute = cl->HasSwitch("get_attribute"); |
| bool set_attribute = cl->HasSwitch("set_attribute"); |
| bool copy = cl->HasSwitch("copy_object"); |
| bool close_all_sessions = cl->HasSwitch("replay_close_all_sessions"); |
| if (!generate && !generate_delete && !vpn && !wifi && !logout && !cleanup && |
| !inject && !list_objects && !import && !digest_test && !list_tokens && |
| !get_attribute && !set_attribute && !copy && !close_all_sessions) { |
| PrintHelp(); |
| return 0; |
| } |
| |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderr); |
| base::TimeTicks start_ticks = base::TimeTicks::Now(); |
| CK_SLOT_ID slot = Initialize(); |
| int tmp_slot = 0; |
| if (cl->HasSwitch("slot") && |
| base::StringToInt(cl->GetSwitchValueASCII("slot"), &tmp_slot)) |
| slot = tmp_slot; |
| LOG(INFO) << "Using slot " << slot; |
| CK_SESSION_HANDLE session = OpenSession(slot); |
| PrintTicks(&start_ticks); |
| string label = "_default"; |
| if (cl->HasSwitch("label")) |
| label = cl->GetSwitchValueASCII("label"); |
| int key_size_bits = 2048; |
| if (cl->HasSwitch("key_size") && |
| !base::StringToInt(cl->GetSwitchValueASCII("key_size"), &key_size_bits)) |
| key_size_bits = 2048; |
| if (generate || generate_delete) { |
| session = Login(slot, false, session); |
| PrintTicks(&start_ticks); |
| GenerateKeyPair(session, key_size_bits, label, generate_delete); |
| PrintTicks(&start_ticks); |
| } else if (inject) { |
| session = Login(slot, false, session); |
| PrintTicks(&start_ticks); |
| InjectRSAKeyPair(session, key_size_bits, label); |
| PrintTicks(&start_ticks); |
| } else if (import) { |
| vector<uint8_t> object_id; |
| if (!base::HexStringToBytes(cl->GetSwitchValueASCII("id"), &object_id)) { |
| LOG(ERROR) << "Invalid arg, expecting hex string for id (like b18aa8)."; |
| exit(-1); |
| } |
| std::string type = cl->GetSwitchValueASCII("type"); |
| std::string path = cl->GetSwitchValueASCII("path"); |
| if (base::EqualsCaseInsensitiveASCII("cert", type)) { |
| ReadInObject(session, path, object_id, kCertificate, false); |
| } else if (base::EqualsCaseInsensitiveASCII("privkey", type)) { |
| ReadInObject(session, path, object_id, kPrivateKey, |
| cl->HasSwitch("force_software")); |
| } else if (base::EqualsCaseInsensitiveASCII("pubkey", type)) { |
| ReadInObject(session, path, object_id, kPublicKey, false); |
| } else { |
| LOG(ERROR) << "Invalid token type."; |
| exit(-1); |
| } |
| PrintTicks(&start_ticks); |
| } |
| if (list_objects) { |
| vector<CK_OBJECT_HANDLE> objects; |
| CK_BBOOL priv_value = CK_FALSE; |
| CK_ATTRIBUTE priv = {CKA_PRIVATE, &priv_value, sizeof(priv_value)}; |
| Find(session, &priv, 1, &objects); |
| printf("Public Objects:\n"); |
| PrintObjects(objects); |
| PrintTicks(&start_ticks); |
| objects.clear(); |
| Login(slot, false, session); |
| priv_value = CK_TRUE; |
| Find(session, &priv, 1, &objects); |
| printf("Private Objects:\n"); |
| PrintObjects(objects); |
| PrintTicks(&start_ticks); |
| } |
| if (vpn || wifi) { |
| bool skip_generate = cl->HasSwitch("skip_generate"); |
| printf("Replay 1 of 2\n"); |
| // No need to login again if --generate or --inject flag is passed |
| // as it's already logged in for this session |
| if (!generate && !inject && !skip_generate) { |
| session = Login(slot, vpn, session); |
| GenerateKeyPair(session, key_size_bits, label, false); |
| } |
| Sign(session, label); |
| PrintTicks(&start_ticks); |
| printf("Replay 2 of 2\n"); |
| CK_SESSION_HANDLE session2 = OpenSession(slot); |
| session2 = Login(slot, vpn, session2); |
| Sign(session2, label); |
| PrintTicks(&start_ticks); |
| C_CloseSession(session2); |
| // Delete the temporary key pair to avoid piling up. |
| if (!generate && !inject && !skip_generate) { |
| DestroyKeyPair(session, label); |
| } |
| } |
| if (digest_test) { |
| const int kNumThreads = 100; |
| std::unique_ptr<DigestTestThread> threads[kNumThreads]; |
| base::PlatformThreadHandle handles[kNumThreads]; |
| for (int i = 0; i < kNumThreads; ++i) { |
| LOG(INFO) << "Creating thread " << i; |
| threads[i].reset(new DigestTestThread(slot)); |
| if (!base::PlatformThread::Create(0, threads[i].get(), &handles[i])) |
| LOG(FATAL) << "Failed to create thread."; |
| } |
| for (int i = 0; i < kNumThreads; ++i) { |
| base::PlatformThread::Join(handles[i]); |
| LOG(INFO) << "Joined thread " << i; |
| } |
| } |
| if (list_tokens) { |
| PrintTokens(); |
| } |
| if (get_attribute) { |
| vector<uint8_t> object_id; |
| if (!base::HexStringToBytes(cl->GetSwitchValueASCII("id"), &object_id)) { |
| LOG(ERROR) << "Invalid arg, expecting hex string for id (like b18aa8)."; |
| exit(-1); |
| } |
| string attribute_string = cl->GetSwitchValueASCII("attribute"); |
| CK_ATTRIBUTE_TYPE attribute; |
| if (!chaps::StringToAttribute(attribute_string, &attribute)) { |
| LOG(ERROR) << "Unable to parse attribute: " << attribute_string; |
| exit(-1); |
| } |
| GetAttribute(session, object_id, attribute, |
| cl->GetSwitchValueASCII("output_format"), |
| cl->GetSwitchValueASCII("type")); |
| } |
| if (set_attribute) { |
| vector<uint8_t> object_id; |
| if (!base::HexStringToBytes(cl->GetSwitchValueASCII("id"), &object_id)) { |
| LOG(ERROR) << "Invalid arg, expecting hex string for id (like b18aa8)."; |
| exit(-1); |
| } |
| string attribute_string = cl->GetSwitchValueASCII("attribute"); |
| CK_ATTRIBUTE_TYPE attribute; |
| if (!chaps::StringToAttribute(attribute_string, &attribute)) { |
| LOG(ERROR) << "Unable to parse attribute: " << attribute_string; |
| exit(-1); |
| } |
| string data_string = cl->GetSwitchValueASCII("data"); |
| vector<uint8_t> data_to_write; |
| if (!base::HexStringToBytes(data_string, &data_to_write)) { |
| LOG(ERROR) << "Invalid hex input data: " << data_string; |
| } |
| SetAttribute(session, object_id, attribute, cl->GetSwitchValueASCII("type"), |
| data_to_write); |
| } |
| if (copy) { |
| // Parse from and to object ID. |
| vector<uint8_t> object_id; |
| if (!base::HexStringToBytes(cl->GetSwitchValueASCII("id"), &object_id)) { |
| LOG(ERROR) << "Invalid arg, expecting hex string for --id (like b18aa8)."; |
| exit(-1); |
| } |
| |
| // Parse the --attr_list switch |
| vector<string> raw_attr_list = |
| base::SplitString(cl->GetSwitchValueASCII("attr_list"), ",", |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| unordered_map<CK_ATTRIBUTE_TYPE, vector<uint8_t>> attr_map; |
| for (const string& attr_pair : raw_attr_list) { |
| vector<string> splitted_attr = base::SplitString( |
| attr_pair, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (splitted_attr.size() != 2) { |
| LOG(ERROR) |
| << "Invalid attribute pair, expected <attr>:<hex value>, got: " |
| << attr_pair; |
| exit(-1); |
| } |
| |
| // Parse the attribute. |
| CK_ATTRIBUTE_TYPE current_attr; |
| if (!chaps::StringToAttribute(splitted_attr[0], ¤t_attr)) { |
| LOG(ERROR) << "Unable to parse attribute: " << splitted_attr[0]; |
| exit(-1); |
| } |
| |
| // Parse the value. |
| vector<uint8_t> attr_value; |
| if (!base::HexStringToBytes(splitted_attr[1], &attr_value)) { |
| LOG(ERROR) << "Invalid attribute value, must be in hex: " |
| << splitted_attr[1]; |
| exit(-1); |
| } |
| |
| // Insert it into the attribute map. |
| if (attr_map.count(current_attr) != 0) { |
| LOG(ERROR) << "Duplicate attribute: " << splitted_attr[0]; |
| exit(-1); |
| } |
| attr_map[current_attr] = attr_value; |
| } |
| CopyObject(session, cl->GetSwitchValueASCII("type"), object_id, attr_map); |
| } |
| if (close_all_sessions) { |
| // This section is used to test that C_CloseAllSessions() behaves correctly. |
| // --use_sessions_loop will create a process that continuously use a |
| // session, to check that it's session is not closed by C_CloseAllSessions() |
| // from another process. This will continue for approximately 10 seconds. |
| // --check_close_all_sessions will check that the current session works, |
| // then call C_CloseAllSessions(), then check that it no longer works. |
| string ipc_file_path = cl->GetSwitchValueASCII("ipc_file"); |
| CHECK(!ipc_file_path.empty()) << "--ipc_file should be specified"; |
| if (cl->HasSwitch("use_sessions_loop")) { |
| return ReplayCloseAllSessionsLoop(session, ipc_file_path); |
| } else if (cl->HasSwitch("check_close_all_sessions")) { |
| return ReplayCloseAllSessionsCheck(session, slot, ipc_file_path); |
| } else { |
| LOG(FATAL) << "--replay_close_all_sessions needs --use_sessions_loop or " |
| "--check_close_all_sessions"; |
| } |
| } |
| if (cleanup) |
| DeleteAllTestKeys(session); |
| TearDown(session, logout); |
| PrintTicks(&start_ticks); |
| } |