// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CRYPTO_PROCESS_BOUND_STRING_H_ #define CRYPTO_PROCESS_BOUND_STRING_H_ #include #include #include "base/check.h" #include "base/containers/span.h" #include "base/feature_list.h" #include "base/gtest_prod_util.h" #include "crypto/crypto_export.h" #include "crypto/features.h" namespace crypto { namespace internal { // Maybe round the size of the data to a size needed for the encrypt or decrypt // operation. Returns the new size, or `size` if no rounding up is needed. CRYPTO_EXPORT size_t MaybeRoundUp(size_t size); // Maybe encrypt a buffer, in place. Returns true if the buffer was successfully // encrypted or false if unsupported by the platform or failed to encrypt. CRYPTO_EXPORT bool MaybeEncryptBuffer(base::span buffer); // Maybe decrypt a buffer, in place. Returns true if the buffer was successfully // decrypted or false if unsupported by the platform or failed to decrypt. CRYPTO_EXPORT bool MaybeDecryptBuffer(base::span buffer); // Securely zero a buffer using a platform specific method. CRYPTO_EXPORT void SecureZeroBuffer(base::span buffer); } // namespace internal // SecureAllocator is used by the SecureString variants below to clear the // memory when the string moves out of scope. template struct CRYPTO_EXPORT SecureAllocator { using value_type = T; SecureAllocator() noexcept = default; T* allocate(std::size_t n) { return std::allocator().allocate(n); } void deallocate(T* p, std::size_t n) noexcept { if (p) { // SAFETY: deallocate() has a fixed prototype from the std library, and // passes an unsafe buffer, so convert it to a base::span here. internal::SecureZeroBuffer(UNSAFE_BUFFERS( base::span(reinterpret_cast(p), n * sizeof(T)))); std::allocator().deallocate(p, n); } } }; // On supported platforms, a process bound string cannot have its content read // by other processes on the system. On unsupported platforms it provides no // difference over a native string except it does more copies. template class CRYPTO_EXPORT ProcessBound { public: using CharType = typename StringType::value_type; ProcessBound(const ProcessBound& other) = default; ProcessBound(ProcessBound&& other) = default; ProcessBound& operator=(const ProcessBound& other) = default; ProcessBound& operator=(ProcessBound&& other) = default; // Create a process bound string. Takes a copy of the string passed in. explicit ProcessBound(const StringType& value) : original_size_(value.size()) { std::vector data(value.begin(), value.end()); if (base::FeatureList::IsEnabled( crypto::features::kProcessBoundStringEncryption)) { data.resize(internal::MaybeRoundUp(data.size())); encrypted_ = internal::MaybeEncryptBuffer(base::as_writable_byte_span(data)); } maybe_encrypted_data_ = std::move(data); } ~ProcessBound() = default; // Return the decrypted string. StringType value() const { return StringType(secure_value()); } // Return the decrypted string as a string that attempts to wipe itself after // use. Prefer over calling `value()` if caller can support it. std::basic_string, SecureAllocator> secure_value() const { if (!encrypted_) { return std::basic_string, SecureAllocator>( maybe_encrypted_data_.data(), original_size_); } // Copy to decrypt in-place. std::basic_string, SecureAllocator> decrypted(maybe_encrypted_data_.begin(), maybe_encrypted_data_.end()); // Attempt to avoid Small String Optimization (SSO) by reserving a larger // allocation than the SSO default, forcing a dynamic allocation to occur, // before any decrypted data is written to the string. This value was // determined empirically. constexpr size_t kSSOMaxSize = 64u; if (decrypted.size() < kSSOMaxSize) { decrypted.reserve(kSSOMaxSize); } CHECK(internal::MaybeDecryptBuffer(base::as_writable_byte_span(decrypted))); decrypted.resize(original_size_); return decrypted; } size_t size() const { return original_size_; } bool empty() const { return size() == 0; } private: FRIEND_TEST_ALL_PREFIXES(ProcessBoundFeatureTest, Encryption); std::vector maybe_encrypted_data_; size_t original_size_; bool encrypted_ = false; }; using ProcessBoundString = ProcessBound; using ProcessBoundWString = ProcessBound; using ProcessBoundU16String = ProcessBound; // SecureString variants here attempt to clean memory for the string data when // the string goes out of scope. However, while in memory it can be read, and if // copied somewhere else, the memory can also be read. This is a defense in // depth hardening and not meant to provide strong security guarantees. using SecureString = std::basic_string, SecureAllocator>; using SecureWString = std::basic_string, SecureAllocator>; using SecureU16String = std::basic_string, SecureAllocator>; } // namespace crypto #endif // CRYPTO_PROCESS_BOUND_STRING_H_