1 // Copyright 2024 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef CRYPTO_PROCESS_BOUND_STRING_H_ 6 #define CRYPTO_PROCESS_BOUND_STRING_H_ 7 8 #include <string> 9 #include <vector> 10 11 #include "base/check.h" 12 #include "base/containers/span.h" 13 #include "base/feature_list.h" 14 #include "base/gtest_prod_util.h" 15 #include "crypto/crypto_export.h" 16 #include "crypto/features.h" 17 18 namespace crypto { 19 20 namespace internal { 21 22 // Maybe round the size of the data to a size needed for the encrypt or decrypt 23 // operation. Returns the new size, or `size` if no rounding up is needed. 24 CRYPTO_EXPORT size_t MaybeRoundUp(size_t size); 25 26 // Maybe encrypt a buffer, in place. Returns true if the buffer was successfully 27 // encrypted or false if unsupported by the platform or failed to encrypt. 28 CRYPTO_EXPORT bool MaybeEncryptBuffer(base::span<uint8_t> buffer); 29 30 // Maybe decrypt a buffer, in place. Returns true if the buffer was successfully 31 // decrypted or false if unsupported by the platform or failed to decrypt. 32 CRYPTO_EXPORT bool MaybeDecryptBuffer(base::span<uint8_t> buffer); 33 34 // Securely zero a buffer using a platform specific method. 35 CRYPTO_EXPORT void SecureZeroBuffer(base::span<uint8_t> buffer); 36 37 } // namespace internal 38 39 // SecureAllocator is used by the SecureString variants below to clear the 40 // memory when the string moves out of scope. 41 template <typename T> 42 struct CRYPTO_EXPORT SecureAllocator { 43 using value_type = T; 44 45 SecureAllocator() noexcept = default; 46 allocateSecureAllocator47 T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); } 48 deallocateSecureAllocator49 void deallocate(T* p, std::size_t n) noexcept { 50 if (p) { 51 // SAFETY: deallocate() has a fixed prototype from the std library, and 52 // passes an unsafe buffer, so convert it to a base::span here. 53 internal::SecureZeroBuffer(UNSAFE_BUFFERS( 54 base::span<uint8_t>(reinterpret_cast<uint8_t*>(p), n * sizeof(T)))); 55 std::allocator<T>().deallocate(p, n); 56 } 57 } 58 }; 59 60 // On supported platforms, a process bound string cannot have its content read 61 // by other processes on the system. On unsupported platforms it provides no 62 // difference over a native string except it does more copies. 63 template <typename StringType> 64 class CRYPTO_EXPORT ProcessBound { 65 public: 66 using CharType = typename StringType::value_type; 67 68 ProcessBound(const ProcessBound& other) = default; 69 ProcessBound(ProcessBound&& other) = default; 70 ProcessBound& operator=(const ProcessBound& other) = default; 71 ProcessBound& operator=(ProcessBound&& other) = default; 72 73 // Create a process bound string. Takes a copy of the string passed in. ProcessBound(const StringType & value)74 explicit ProcessBound(const StringType& value) 75 : original_size_(value.size()) { 76 std::vector<CharType> data(value.begin(), value.end()); 77 if (base::FeatureList::IsEnabled( 78 crypto::features::kProcessBoundStringEncryption)) { 79 data.resize(internal::MaybeRoundUp(data.size())); 80 encrypted_ = 81 internal::MaybeEncryptBuffer(base::as_writable_byte_span(data)); 82 } 83 maybe_encrypted_data_ = std::move(data); 84 } 85 86 ~ProcessBound() = default; 87 88 // Return the decrypted string. value()89 StringType value() const { return StringType(secure_value()); } 90 91 // Return the decrypted string as a string that attempts to wipe itself after 92 // use. Prefer over calling `value()` if caller can support it. 93 std::basic_string<CharType, 94 std::char_traits<CharType>, 95 SecureAllocator<CharType>> secure_value()96 secure_value() const { 97 if (!encrypted_) { 98 return std::basic_string<CharType, std::char_traits<CharType>, 99 SecureAllocator<CharType>>( 100 maybe_encrypted_data_.data(), original_size_); 101 } 102 103 // Copy to decrypt in-place. 104 std::basic_string<CharType, std::char_traits<CharType>, 105 SecureAllocator<CharType>> 106 decrypted(maybe_encrypted_data_.begin(), maybe_encrypted_data_.end()); 107 // Attempt to avoid Small String Optimization (SSO) by reserving a larger 108 // allocation than the SSO default, forcing a dynamic allocation to occur, 109 // before any decrypted data is written to the string. This value was 110 // determined empirically. 111 constexpr size_t kSSOMaxSize = 64u; 112 if (decrypted.size() < kSSOMaxSize) { 113 decrypted.reserve(kSSOMaxSize); 114 } 115 CHECK(internal::MaybeDecryptBuffer(base::as_writable_byte_span(decrypted))); 116 decrypted.resize(original_size_); 117 return decrypted; 118 } 119 size()120 size_t size() const { return original_size_; } empty()121 bool empty() const { return size() == 0; } 122 123 private: 124 FRIEND_TEST_ALL_PREFIXES(ProcessBoundFeatureTest, Encryption); 125 std::vector<CharType> maybe_encrypted_data_; 126 size_t original_size_; 127 bool encrypted_ = false; 128 }; 129 130 using ProcessBoundString = ProcessBound<std::string>; 131 using ProcessBoundWString = ProcessBound<std::wstring>; 132 using ProcessBoundU16String = ProcessBound<std::u16string>; 133 134 // SecureString variants here attempt to clean memory for the string data when 135 // the string goes out of scope. However, while in memory it can be read, and if 136 // copied somewhere else, the memory can also be read. This is a defense in 137 // depth hardening and not meant to provide strong security guarantees. 138 using SecureString = 139 std::basic_string<std::string::value_type, 140 std::char_traits<std::string::value_type>, 141 SecureAllocator<std::string::value_type>>; 142 using SecureWString = 143 std::basic_string<std::wstring::value_type, 144 std::char_traits<std::wstring::value_type>, 145 SecureAllocator<std::wstring::value_type>>; 146 using SecureU16String = 147 std::basic_string<std::u16string::value_type, 148 std::char_traits<std::u16string::value_type>, 149 SecureAllocator<std::u16string::value_type>>; 150 151 } // namespace crypto 152 153 #endif // CRYPTO_PROCESS_BOUND_STRING_H_ 154