blob: 2cb728cf3482abb863350aa4196efbf453378807 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRYPTOHOME_USER_SECRET_STASH_DECRYPTED_H_
#define CRYPTOHOME_USER_SECRET_STASH_DECRYPTED_H_
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <brillo/secure_blob.h>
#include "cryptohome/auth_factor/type.h"
#include "cryptohome/recoverable_key_store/type.h"
#include "cryptohome/storage/file_system_keyset.h"
#include "cryptohome/user_secret_stash/encrypted.h"
#include "cryptohome/user_secret_stash/storage.h"
namespace cryptohome {
// This class represents a decrypted User Secret Stash (USS). It is built around
// the encrypted version of the class but it also has (and provides) access to
// the decrypted secrets contained within.
//
// The core interface of the class is read-only, and so does not provide any
// functions that allow you to directly modify the USS contents (e.g. by adding
// more wrapping keys). Modifications are instead done via a Transaction class
// in order to enforce atomicity: either your complete set of changes are
// applied or none are. Normal write operations would look something like:
//
// {
// auto transaction = decrypted_uss.StartTransaction();
// CryptohomeStatus result1 = transaction.InsertWrappedMainKey(id1, key1);
// /* check result status */
// CryptohomeStatus result2 = transaction.InsertWrappedMainKey(id2, key2);
// /* check result status */
// CryptohomeStatus commit_result = std::move(transaction).Commit();
// /* if commit_result is OK, this is the point where the modifications will
// be visible in the starting decrypted_uss object */
// }
//
// The enclosing {} around the entire transaction are not strictly necessary
// but they do help avoid accidentally using the transaction after the commit
// and provide a useful visual indicator of the scope of the transaction.
//
// Note that when an individual operation on a transaction fails, that does not
// fail the entire transaction. It just means that the individual mutation
// operation did not apply and will not show up. While abandoning the
// transaction on any failure is the most common and useful pattern you can
// chose to continue and commit modifications that did succeed.
class DecryptedUss {
public:
class Transaction {
public:
// This object is deliberately not copyable or movable. Transactions are
// intended to be short lived and have a direct reference to an underlying
// DecryptedUss object and so they should be created and store in a local
// variable, not living beyond the immediately visible scope.
Transaction(const Transaction&) = delete;
Transaction& operator=(const Transaction&) = delete;
// Insert or assign a new wrapped main key with the specified wrapping ID
// and key. The wrapping key must be of kAesGcm256KeySize length.
//
// The difference between insert and assign is that insert considers it an
// error if a wrapped key with the given ID already exists whereas assign
// will unconditionally overwrite it.
CryptohomeStatus InsertWrappedMainKey(
std::string wrapping_id, const brillo::SecureBlob& wrapping_key);
CryptohomeStatus AssignWrappedMainKey(
std::string wrapping_id, const brillo::SecureBlob& wrapping_key);
// Initialize the fingerprint rater limiter ID in USS. Returns an error if
// the ID is already initialized.
CryptohomeStatus InitializeFingerprintRateLimiterId(uint64_t id);
// Update the legacy fingerprint migration rollout number. The new value
// must be greater than the existing value, if there is one.
CryptohomeStatus IncreaseLegacyFingerprintMigrationRolloutTo(
uint64_t rollout);
// Insert or assign a new reset secret for a given wrapping ID.
//
// The difference between insert and assign is that insert considers it an
// error if a secret with the given ID already exists whereas assign will
// unconditionally overwrite it.
CryptohomeStatus InsertResetSecret(std::string wrapping_id,
brillo::SecureBlob secret);
CryptohomeStatus AssignResetSecret(std::string wrapping_id,
brillo::SecureBlob secret);
// Renames an existing wrapping ID from the given old value to a new value.
// This does not modify any of the keys or secrets stored under this ID in
// any way. Returns an error if either the old ID doesn't exist or the new
// ID already does.
CryptohomeStatus RenameWrappingId(const std::string& old_wrapping_id,
std::string new_wrapping_id);
// Removes all of the keys and secrets stored under the given wrapping ID.
// Returns an error if the given ID does not exist.
CryptohomeStatus RemoveWrappingId(const std::string& wrapping_id);
// Insert a new rate limiter reset secret for a given type of factor.
// Returns an error if the secret could not be inserted, which includes the
// case where a secret already exists.
CryptohomeStatus InsertRateLimiterResetSecret(
AuthFactorType auth_factor_type, brillo::SecureBlob secret);
// Attempt to commit the changes to the underlying DecryptedUss. On success
// this will return OK and the underlying store will be modified; on failure
// and error will be returned an none of the changes from the transaction
// will have been applied. Writing the resulting changes out to storage will
// also be considered a part of the commit sequence and the commit will only
// succeed if the changes are able to be persisted. If the commit fails in
// that case than both the in-memory and in-storage copies should remain
// unmodified.
//
// Note that there is no equivalent "rollback" operation. To abandon a
// transaction without committing any modifications you can simply discard
// the Transaction object.
CryptohomeStatus Commit() &&;
private:
// The DecryptedUss class needs to be able to construct Transaction objects.
friend class DecryptedUss;
Transaction(DecryptedUss& uss,
EncryptedUss::Container container,
std::map<std::string, brillo::SecureBlob> reset_secrets,
std::map<AuthFactorType, brillo::SecureBlob>
rate_limiter_reset_secrets);
DecryptedUss& uss_;
// Starts as a copy of the original encrypted container. The unencrypted
// portions will be modified by the transaction as they are made, but the
// encrypted portion will only be rewritten during the Commit process.
EncryptedUss::Container container_;
// Copies of the original decrypted secrets with the modifications from the
// transaction. Will be written over the originals by a successful Commit.
std::map<std::string, brillo::SecureBlob> reset_secrets_;
std::map<AuthFactorType, brillo::SecureBlob> rate_limiter_reset_secrets_;
};
// Define an error type that combines a CryptohomeStatus with an EncryptedUss.
// This is used by several factory functions which take an EncryptedUss as a
// parameter, to give them a way to return the EncryptedUss object to the
// caller on failure.
//
// In theory this could be done as a subclass or extension of CryptohomeError
// but in practice this ends up being somewhat difficult to use interchangably
// with CryptohomeStatus and so barring some enhancements to StackableError
// this is probably the best we can do.
struct FailedDecrypt {
CryptohomeStatus status;
EncryptedUss encrypted;
};
// Define a variant type that's either a DecryptedUss or a FailedDecrypt. This
// is intended to be somewhat morally equivalent to
// CryptohomeStatusOr<DecryptedUss> but with FailedDecrypt instead of a
// status.
using FailedDecryptOrDecryptedUss = std::variant<DecryptedUss, FailedDecrypt>;
// Create a new stash storing the given filesystem keyset, encrypted with the
// given main key. Note that this will not persist the created USS to storage
// yet, as a created USS without any wrapped keyset should only be persisted
// after adding the first auth factor. It's fine that the in-memory USS isn't
// consistent with the disk in this case, as if the USS doesn't eventually get
// persisted, the user isn't created successfully so the inconsistency doesn't
// matter.
static CryptohomeStatusOr<DecryptedUss> CreateWithMainKey(
UserUssStorage storage,
FileSystemKeyset file_system_keyset,
brillo::SecureBlob main_key);
// This will generate a random main key and call CreateWithMainKey.
static CryptohomeStatusOr<DecryptedUss> CreateWithRandomMainKey(
UserUssStorage storage, FileSystemKeyset file_system_keyset);
// Attempt to decrypt USS using using the main key.
static CryptohomeStatusOr<DecryptedUss> FromStorageUsingMainKey(
UserUssStorage storage, brillo::SecureBlob main_key);
// Attempt to decrypt USS using using a wrapped key.
static CryptohomeStatusOr<DecryptedUss> FromStorageUsingWrappedKey(
UserUssStorage storage,
const std::string& wrapping_id,
const brillo::SecureBlob& wrapping_key);
// Attempt to decrypt USS using an existing encrypted instance and the given
// wrapped key. On success, the returned variant will contain a DecryptedUss.
// On failure the returned variant will contain a not-OK status and the
// original encrypted USS.
static FailedDecryptOrDecryptedUss FromEncryptedUssUsingWrappedKey(
UserUssStorage storage,
EncryptedUss encrypted,
const std::string& wrapping_id,
const brillo::SecureBlob& wrapping_key);
DecryptedUss(const DecryptedUss&) = delete;
DecryptedUss& operator=(const DecryptedUss&) = delete;
DecryptedUss(DecryptedUss&&) = default;
DecryptedUss& operator=(DecryptedUss&&) = default;
// Simple read accessor for the underlying encrypted USS data.
const EncryptedUss& encrypted() const& { return encrypted_; }
// Destructive accessor that releases the underlying encrypted USS data to the
// caller. Note that this invalidates the DecryptedUss object. The use case
// is for when you intend to destroy |this| but need to extract and keep the
// original encrypted object.
EncryptedUss encrypted() && { return std::move(encrypted_); }
const FileSystemKeyset& file_system_keyset() const {
return file_system_keyset_;
}
const brillo::SecureBlob& key_derivation_seed() const {
return key_derivation_seed_;
}
// Returns the reset secret associated with the given label, or null if there
// is no such secret.
std::optional<brillo::SecureBlob> GetResetSecret(
const std::string& label) const;
// Returns the rate limiter reset secret associated with the given type of
// auth factor, or null if there is no such secret.
std::optional<brillo::SecureBlob> GetRateLimiterResetSecret(
AuthFactorType auth_factor_type) const;
// The SecurityDomainKeys object derived from the key derivation seed.
const SecurityDomainKeys* GetSecurityDomainKeys() const;
// Begin a transaction which can be used to modify this object.
Transaction StartTransaction();
private:
// Given an EncryptedUss and a main key, attempt to decrypt it and construct
// the DecryptedUss. New fields might be introduced to the USS container:
// sometimes the default flatbuffer value (like empty blobs) are suitable,
// while sometimes new fields should be initialized if they don't exist (like
// fixed secrets). We perform the initialization routine of new fields in this
// method, and if such routine is performed, the changes would be committed to
// |storage|.
static FailedDecryptOrDecryptedUss FromEncryptedUss(
UserUssStorage storage,
EncryptedUss encrypted,
brillo::SecureBlob main_key);
DecryptedUss(
UserUssStorage storage,
EncryptedUss encrypted,
brillo::SecureBlob main_key,
FileSystemKeyset file_system_keyset,
std::map<std::string, brillo::SecureBlob> reset_secrets,
std::map<AuthFactorType, brillo::SecureBlob> rate_limiter_reset_secrets,
brillo::SecureBlob key_derivation_seed);
// The underlying storage of the decrypted USS instance.
UserUssStorage storage_;
// The underlying raw data.
EncryptedUss encrypted_;
// The main key used to encrypt and decrypt the payload.
brillo::SecureBlob main_key_;
// Keys registered with the kernel to decrypt files and file names, together
// with corresponding salts and signatures.
FileSystemKeyset file_system_keyset_;
// The reset secrets corresponding to each auth factor, by label.
std::map<std::string, brillo::SecureBlob> reset_secrets_;
// The reset secrets corresponding to each auth factor type's rate limiter.
std::map<AuthFactorType, brillo::SecureBlob> rate_limiter_reset_secrets_;
// The seed to derive other key materials.
brillo::SecureBlob key_derivation_seed_;
// Cache the security domain keys after they're first calculated to prevent
// re-calculation.
mutable std::optional<SecurityDomainKeys> security_domain_keys_;
};
} // namespace cryptohome
#endif // CRYPTOHOME_USER_SECRET_STASH_DECRYPTED_H_