namespace DodoSSH.Domain; /// /// An SSH host. /// /// /// /// The only vault item type in M1. Everything sensitive — username, notes, jump chain, SSH /// options — lives inside . There is deliberately no plaintext label: /// access-control administration happens in the client, which can decrypt names, so the server /// never needs a searchable title. /// /// /// and are the one deliberate plaintext concession, and /// only when is set. The relay must resolve its target server-side or /// it becomes an authenticated open TCP proxy into the operator's own network. A database CHECK /// constraint enforces the pairing so it cannot drift. See ADR 0004. /// /// public sealed class SshHost : IVaultItem { /// Primary key. UUIDv7, generated by the client so items can be created offline. public Guid Id { get; set; } /// Owning vault. public Guid VaultId { get; set; } /// Owning vault. public Vault? Vault { get; set; } /// The encrypted item: a DSH1 envelope. Opaque to the server. public byte[] Payload { get; set; } = []; /// The item's data key, wrapped under the vault key. Opaque. public byte[]? DataKeyWrap { get; set; } /// /// Reserved for per-item content keys wrapped to individual users, which is what will make /// per-item access control cryptographic rather than server-enforced. Present from the first /// migration so that lands without a migration; see docs/crypto.md §3. /// public Guid? ContentKeyId { get; set; } /// Vault key generation this payload was encrypted under. public int KeyGeneration { get; set; } /// AAD rule version, enabling a lazy re-encrypt-on-write migration later. public short PayloadAadVersion { get; set; } /// Whether this host may be dialled through the server relay. public bool RelayEnabled { get; set; } /// Target hostname. Permitted only when is set. public string? Hostname { get; set; } /// Target port. Permitted only when is set. public int? Port { get; set; } /// Owning group, for tree placement. Groups arrive in M2. public Guid? GroupId { get; set; } /// /// Client-visible, monotonic item version. Used for optimistic concurrency on push, and /// deliberately distinct from the internal xmin guard, which is never exposed because /// it is not stable across VACUUM FREEZE. /// public int Version { get; set; } /// Latest change-log sequence touching this row, so a delta pull can join directly. public long ChangeSequence { get; set; } /// Creation timestamp. public DateTimeOffset CreatedAtUtc { get; set; } /// Last modification timestamp. public DateTimeOffset UpdatedAtUtc { get; set; } /// /// Soft-delete marker. Deletes are tombstones: a client that has been offline must be able to /// learn an item went away, and a vanished row is indistinguishable from one never seen. /// public DateTimeOffset? DeletedAtUtc { get; set; } /// Who created it. public Guid CreatedByUserId { get; set; } /// Who last modified it. public Guid UpdatedByUserId { get; set; } } /// /// An SSH key pair, held as ciphertext. /// /// /// /// The private key, its passphrase, its label and its comment are all inside . This /// row is the reason the vault is worth having — a key that syncs between a user's machines and can later be /// shared with a teammate — and it is also the row that would hurt most if the server could read it, so /// there is deliberately not one plaintext column of substance. /// /// /// Notably absent: the relay trio. A key is not something the server dials, so the plaintext concession /// ADR 0004 makes for has no analogue here and no reason to exist. That is the whole /// argument for a separate table rather than one wide item table: the columns a host needs are columns a key /// must never have, and a shared table could only make them nullable and trust the code. /// /// /// is the single exception, and it stays null until something needs it. /// The contract reserved the slot for showing which key a host is configured to use without decrypting /// every key first; write it only when that feature lands, and never derive anything security-relevant from /// it, because a fingerprint the server stores is a fingerprint the server chose. /// /// public sealed class VaultSshKey : IVaultItem { /// Primary key. UUIDv7, generated by the client so keys can be created offline. public Guid Id { get; set; } /// Owning vault. public Guid VaultId { get; set; } /// Owning vault. public Vault? Vault { get; set; } /// The encrypted key material: a DSH1 envelope. Opaque to the server. public byte[] Payload { get; set; } = []; /// The item's data key, wrapped under the vault key. Opaque. public byte[]? DataKeyWrap { get; set; } /// Reserved for per-item content keys wrapped to individual users; see docs/crypto.md §3. public Guid? ContentKeyId { get; set; } /// Vault key generation this payload was encrypted under. public int KeyGeneration { get; set; } /// AAD rule version, enabling a lazy re-encrypt-on-write migration later. public short PayloadAadVersion { get; set; } /// /// OpenSSH-style SHA256:base64 fingerprint of the public half, when a client publishes it. /// /// /// Plaintext, and therefore opt-in and currently unused. A public key fingerprint is not a secret, but /// it is an identifier that links a vault to a machine's authorized_keys, so it is stored only /// when a feature needs it rather than because it is harmless. /// public string? PublicKeyFingerprint { get; set; } /// Client-visible, monotonic item version, used for expectedVersion checks. public int Version { get; set; } /// Latest change-log sequence touching this row, so a delta pull can join directly. public long ChangeSequence { get; set; } /// Creation timestamp. public DateTimeOffset CreatedAtUtc { get; set; } /// Last modification timestamp. public DateTimeOffset UpdatedAtUtc { get; set; } /// Soft-delete marker; a tombstone, so an offline client learns the key went away. public DateTimeOffset? DeletedAtUtc { get; set; } /// Who created it. public Guid CreatedByUserId { get; set; } /// Who last modified it. public Guid UpdatedByUserId { get; set; } } /// /// A stored username and password, as ciphertext. /// /// /// /// Its own table for the same reason is: the columns a host needs are columns a /// credential must never have. There is no relay trio here, and unlike a key there is not even a fingerprint /// — nothing about a password is safe to hold in the clear, not its length, not a hash, not a hint. So this /// row is an opaque envelope and its bookkeeping, and that is the whole design. /// /// /// The server cannot enforce anything about the contents, and should not pretend to. Whether a /// credential has a username, whether its password is empty, whether it is still valid — all of that is /// inside the payload and belongs to the client. The one thing this row asserts is that the ciphertext /// belongs to a vault and carries a version, which is what the write path needs to order changes. /// /// public sealed class VaultCredential : IVaultItem { /// Primary key. UUIDv7, generated by the client so credentials can be created offline. public Guid Id { get; set; } /// Owning vault. public Guid VaultId { get; set; } /// Owning vault. public Vault? Vault { get; set; } /// The encrypted credential: a DSH1 envelope. Opaque to the server. public byte[] Payload { get; set; } = []; /// The item's data key, wrapped under the vault key. Opaque. public byte[]? DataKeyWrap { get; set; } /// Reserved for per-item content keys wrapped to individual users; see docs/crypto.md §3. public Guid? ContentKeyId { get; set; } /// Vault key generation this payload was encrypted under. public int KeyGeneration { get; set; } /// AAD rule version, enabling a lazy re-encrypt-on-write migration later. public short PayloadAadVersion { get; set; } /// Client-visible, monotonic item version, used for expectedVersion checks. public int Version { get; set; } /// Latest change-log sequence touching this row, so a delta pull can join directly. public long ChangeSequence { get; set; } /// Creation timestamp. public DateTimeOffset CreatedAtUtc { get; set; } /// Last modification timestamp. public DateTimeOffset UpdatedAtUtc { get; set; } /// Soft-delete marker; a tombstone, so an offline client learns the credential went away. public DateTimeOffset? DeletedAtUtc { get; set; } /// Who created it. public Guid CreatedByUserId { get; set; } /// Who last modified it. public Guid UpdatedByUserId { get; set; } }