blob: 729f75126d8edfa19cb449577465f6108fa89635 [file] [log] [blame]
// 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 <string>
#include <vector>
#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<uint8_t> 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<uint8_t> buffer);
// Securely zero a buffer using a platform specific method.
CRYPTO_EXPORT void SecureZeroBuffer(base::span<uint8_t> buffer);
} // namespace internal
// SecureAllocator is used by the SecureString variants below to clear the
// memory when the string moves out of scope.
template <typename T>
struct CRYPTO_EXPORT SecureAllocator {
using value_type = T;
SecureAllocator() noexcept = default;
T* allocate(std::size_t n) { return std::allocator<T>().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<uint8_t>(reinterpret_cast<uint8_t*>(p), n * sizeof(T))));
std::allocator<T>().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 <typename StringType>
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<CharType> 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<CharType,
std::char_traits<CharType>,
SecureAllocator<CharType>>
secure_value() const {
if (!encrypted_) {
return std::basic_string<CharType, std::char_traits<CharType>,
SecureAllocator<CharType>>(
maybe_encrypted_data_.data(), original_size_);
}
// Copy to decrypt in-place.
std::basic_string<CharType, std::char_traits<CharType>,
SecureAllocator<CharType>>
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<CharType> maybe_encrypted_data_;
size_t original_size_;
bool encrypted_ = false;
};
using ProcessBoundString = ProcessBound<std::string>;
using ProcessBoundWString = ProcessBound<std::wstring>;
using ProcessBoundU16String = ProcessBound<std::u16string>;
// 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<std::string::value_type,
std::char_traits<std::string::value_type>,
SecureAllocator<std::string::value_type>>;
using SecureWString =
std::basic_string<std::wstring::value_type,
std::char_traits<std::wstring::value_type>,
SecureAllocator<std::wstring::value_type>>;
using SecureU16String =
std::basic_string<std::u16string::value_type,
std::char_traits<std::u16string::value_type>,
SecureAllocator<std::u16string::value_type>>;
} // namespace crypto
#endif // CRYPTO_PROCESS_BOUND_STRING_H_