| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/chaps_util/chaps_util_impl.h" |
| |
| #include <dlfcn.h> |
| #include <keyhi.h> |
| #include <pk11pub.h> |
| #include <pkcs11.h> |
| #include <pkcs11t.h> |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chromeos/ash/components/chaps_util/chaps_slot_session.h" |
| #include "crypto/chaps_support.h" |
| #include "crypto/scoped_nss_types.h" |
| #include "third_party/boringssl/src/include/openssl/base.h" |
| #include "third_party/boringssl/src/include/openssl/mem.h" |
| #include "third_party/boringssl/src/include/openssl/pkcs8.h" |
| #include "third_party/boringssl/src/include/openssl/stack.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // TODO(b/202374261): Move these into a shared header. |
| // Signals to chaps that a generated key should be software-backed. |
| constexpr CK_ATTRIBUTE_TYPE kForceSoftwareAttribute = CKA_VENDOR_DEFINED + 4; |
| // Chaps sets this for keys that are software-backed. |
| constexpr CK_ATTRIBUTE_TYPE kKeyInSoftware = CKA_VENDOR_DEFINED + 5; |
| struct KeyPairHandles { |
| CK_OBJECT_HANDLE public_key; |
| CK_OBJECT_HANDLE private_key; |
| }; |
| |
| using Pkcs11Operation = base::RepeatingCallback<CK_RV()>; |
| |
| // Performs |operation| and handles return values indicating that the PKCS11 |
| // session has been closed by attempting to re-open the |chaps_session|. |
| // This is useful because the session could be closed e.g. because NSS could |
| // have called C_CloseAllSessions. |
| bool PerformWithRetries(ChapsSlotSession* chaps_session, |
| std::string_view operation_name, |
| const Pkcs11Operation& operation) { |
| const int kMaxAttempts = 5; |
| |
| for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { |
| CK_RV result = operation.Run(); |
| if (result == CKR_OK) { |
| return true; |
| } |
| if (result != CKR_SESSION_HANDLE_INVALID && result != CKR_SESSION_CLOSED) { |
| LOG(ERROR) << operation_name << " failed with " << result; |
| return false; |
| } |
| if (!chaps_session->ReopenSession()) { |
| return false; |
| } |
| } |
| LOG(ERROR) << operation_name << " failed"; |
| return false; |
| } |
| |
| // Uses |chaps_session| to generate a software-backed RSA key pair with modulus |
| // length |num_bits|. |
| std::optional<KeyPairHandles> GenerateSoftwareBackedRSAKeyPair( |
| ChapsSlotSession* chaps_session, |
| uint16_t num_bits) { |
| CK_ULONG modulus_bits = num_bits; |
| CK_BBOOL true_value = CK_TRUE; |
| CK_BBOOL false_value = CK_FALSE; |
| CK_BYTE public_exponent[3] = {0x01, 0x00, 0x01}; // 65537 |
| |
| // Public key attributes |
| // Note: CKA_ID is set later (computed from the public key modulus) and |
| // CKA_LABEL is not set to match NSS behavior |
| // (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11_Implement). |
| CK_ATTRIBUTE pub_attributes[] = { |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &false_value, sizeof(false_value)}, |
| {CKA_VERIFY, &true_value, sizeof(true_value)}, |
| {CKA_MODULUS_BITS, &modulus_bits, sizeof(modulus_bits)}, |
| {CKA_PUBLIC_EXPONENT, public_exponent, sizeof(public_exponent)}}; |
| |
| // Private key attributes |
| // Note: CKA_ID is set later (computed from the public key modulus) and |
| // CKA_LABEL is not set to match NSS behavior |
| // (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11_Implement). |
| CK_ATTRIBUTE priv_attributes[] = { |
| {CKA_TOKEN, &true_value, sizeof(true_value)}, |
| {CKA_PRIVATE, &true_value, sizeof(true_value)}, |
| {CKA_SENSITIVE, &true_value, sizeof(true_value)}, |
| {CKA_EXTRACTABLE, &false_value, sizeof(false_value)}, |
| {kForceSoftwareAttribute, &true_value, sizeof(true_value)}, |
| {CKA_SIGN, &true_value, sizeof(true_value)}}; |
| CK_MECHANISM mechanism = {CKM_RSA_PKCS_KEY_PAIR_GEN, /* pParameter */ nullptr, |
| /* ulParameterLen*/ 0}; |
| |
| KeyPairHandles key_pair; |
| |
| if (!PerformWithRetries( |
| chaps_session, "GenerateKeyPair", |
| base::BindRepeating(&ChapsSlotSession::GenerateKeyPair, |
| base::Unretained(chaps_session), &mechanism, |
| pub_attributes, std::size(pub_attributes), |
| priv_attributes, std::size(priv_attributes), |
| &(key_pair.public_key), |
| &(key_pair.private_key)))) { |
| return {}; |
| } |
| return key_pair; |
| } |
| |
| // Read the modulus of the public key identified by |pub_key_handle| and return |
| // it. |
| std::optional<std::vector<CK_BYTE>> ExtractModulus( |
| ChapsSlotSession* chaps_session, |
| CK_OBJECT_HANDLE pub_key_handle) { |
| std::vector<CK_BYTE> modulus(256); |
| CK_ATTRIBUTE attrs_get_modulus[] = { |
| {CKA_MODULUS, modulus.data(), modulus.size()}}; |
| if (!PerformWithRetries( |
| chaps_session, "GetAttributeValue", |
| base::BindRepeating(&ChapsSlotSession::GetAttributeValue, |
| base::Unretained(chaps_session), pub_key_handle, |
| attrs_get_modulus, |
| std::size(attrs_get_modulus)))) { |
| return {}; |
| } |
| return modulus; |
| } |
| |
| crypto::ScopedSECItem MakeIdFromPubKeyNss( |
| const std::vector<uint8_t>& public_key_bytes) { |
| SECItem secitem_modulus; |
| secitem_modulus.data = const_cast<uint8_t*>(public_key_bytes.data()); |
| secitem_modulus.len = public_key_bytes.size(); |
| return crypto::ScopedSECItem(PK11_MakeIDFromPubKey(&secitem_modulus)); |
| } |
| |
| // Read the modulus of the public key identified by |pub_key_handle| and return |
| // it. |
| std::optional<bool> IsKeySoftwareBacked(ChapsSlotSession* chaps_session, |
| CK_OBJECT_HANDLE private_key_handle) { |
| CK_BBOOL key_in_software = CK_FALSE; |
| CK_ATTRIBUTE attrs_get_key_in_software[] = { |
| {kKeyInSoftware, &key_in_software, sizeof(key_in_software)}}; |
| if (!PerformWithRetries( |
| chaps_session, "GetAttributeValue", |
| base::BindRepeating(&ChapsSlotSession::GetAttributeValue, |
| base::Unretained(chaps_session), |
| private_key_handle, attrs_get_key_in_software, |
| std::size(attrs_get_key_in_software)))) { |
| return {}; |
| } |
| return key_in_software; |
| } |
| |
| // Create the CKA_ID value that NSS would use for |key_pair| and return it. |
| crypto::ScopedSECItem CreateNssCkaId(ChapsSlotSession* chaps_session, |
| const KeyPairHandles& key_pair) { |
| auto modulus = ExtractModulus(chaps_session, key_pair.public_key); |
| if (!modulus) { |
| return nullptr; |
| } |
| return MakeIdFromPubKeyNss(modulus.value()); |
| } |
| |
| // Set the CKA_ID attribute of the public and private key objects in |key_pair| |
| // to |cka_id|. |
| bool SetCkaId(ChapsSlotSession* chaps_session, |
| KeyPairHandles& key_pair, |
| SECItem* cka_id) { |
| CK_ATTRIBUTE attrs_set_id[] = {{CKA_ID, cka_id->data, cka_id->len}}; |
| if (!PerformWithRetries( |
| chaps_session, "SetAttributeValue", |
| base::BindRepeating(&ChapsSlotSession::SetAttributeValue, |
| base::Unretained(chaps_session), |
| key_pair.private_key, attrs_set_id, |
| std::size(attrs_set_id)))) { |
| return false; |
| } |
| if (!PerformWithRetries( |
| chaps_session, "SetAttributeValue", |
| base::BindRepeating(&ChapsSlotSession::SetAttributeValue, |
| base::Unretained(chaps_session), |
| key_pair.public_key, attrs_set_id, |
| std::size(attrs_set_id)))) { |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| ChapsUtilImpl::ChapsUtilImpl( |
| std::unique_ptr<ChapsSlotSessionFactory> chaps_slot_session_factory) |
| : chaps_slot_session_factory_(std::move(chaps_slot_session_factory)) {} |
| ChapsUtilImpl::~ChapsUtilImpl() = default; |
| |
| bool ChapsUtilImpl::GenerateSoftwareBackedRSAKey( |
| PK11SlotInfo* slot, |
| uint16_t num_bits, |
| crypto::ScopedSECKEYPublicKey* out_public_key, |
| crypto::ScopedSECKEYPrivateKey* out_private_key) { |
| DCHECK(out_public_key); |
| DCHECK(out_private_key); |
| |
| std::unique_ptr<ChapsSlotSession> chaps_session = |
| GetChapsSlotSessionForSlot(slot); |
| if (!chaps_session) { |
| return false; |
| } |
| |
| std::optional<KeyPairHandles> key_pair = |
| GenerateSoftwareBackedRSAKeyPair(chaps_session.get(), num_bits); |
| if (!key_pair) { |
| return false; |
| } |
| |
| // Safety check that software-backed key generation was triggered. |
| std::optional<bool> is_software_backed = |
| IsKeySoftwareBacked(chaps_session.get(), key_pair->private_key); |
| if (!is_software_backed || !is_software_backed.value()) { |
| return false; |
| } |
| |
| crypto::ScopedSECItem cka_id = |
| CreateNssCkaId(chaps_session.get(), key_pair.value()); |
| if (!cka_id) { |
| return false; |
| } |
| if (!SetCkaId(chaps_session.get(), key_pair.value(), cka_id.get())) { |
| return false; |
| } |
| |
| out_private_key->reset(PK11_FindKeyByKeyID(slot, cka_id.get(), nullptr)); |
| if (!*out_private_key) { |
| LOG(ERROR) << "Failed to find private key."; |
| return false; |
| } |
| out_public_key->reset(SECKEY_ConvertToPublicKey(out_private_key->get())); |
| if (!*out_public_key) { |
| LOG(ERROR) << "Failed to extract public key."; |
| return false; |
| } |
| return true; |
| } |
| |
| std::unique_ptr<ChapsSlotSession> ChapsUtilImpl::GetChapsSlotSessionForSlot( |
| PK11SlotInfo* slot) { |
| if (!slot || (!is_chaps_provided_slot_for_testing_ && |
| !crypto::IsSlotProvidedByChaps(slot))) { |
| return nullptr; |
| } |
| |
| // Note that ChapsSlotSession(Factory) expects something else to have called |
| // C_Initialize. It is a safe assumption that NSS has called C_Initialize for |
| // chaps if |slot| is actually a chaps-provided slot, which is verified above. |
| return chaps_slot_session_factory_->CreateChapsSlotSession( |
| PK11_GetSlotID(slot)); |
| } |
| |
| } // namespace chromeos |