namespace DodoSSH.Contracts; /// /// Argon2id parameters as stored and transmitted. /// /// /// Stored in plaintext per wrap row. Salts are not secrets, and keeping the parameters with the /// wrap makes raising them later a per-user, unlock-time migration rather than a breaking /// change — an older client can still open its own wrap. /// /// is kibibytes, matching both the storage column and libsodium. /// Client code should go through Argon2Profile rather than handling the number directly; /// see docs/crypto.md §2 for why that unit needs guarding. /// /// /// KDF identifier. Currently always argon2id. /// 16-byte salt. /// Memory cost in KiB. /// Number of passes. /// Lanes. Always 1; libsodium supports no other value. public sealed record KdfParameters( string Algorithm, byte[] Salt, int MemoryKibibytes, int Passes, int Parallelism); /// /// The self-describing, signed statement binding a user to their public keys. /// /// /// Its SHA-256 is used as the nonce of a fresh OIDC authorization, so the resulting ID /// token is signed by the identity provider over exactly these keys. That is what stops the /// DodoSSH server fabricating a key for a user who never enrolled. See ADR 0001. /// /// Statement format version. /// OIDC issuer. /// OIDC subject. /// Email at enrollment time, for display only. /// X25519 public key, 32 bytes. /// Ed25519 public key, 32 bytes. /// Generation of this key pair, starting at 1. /// When the client generated the keys. /// Human-readable name of the enrolling device. public sealed record KeyStatement( int Version, string Issuer, string Subject, string? Email, byte[] EncryptionPublicKey, byte[] SigningPublicKey, int KeyGeneration, DateTimeOffset CreatedAt, string DeviceName); /// /// The personal vault a client creates as part of enrolling. /// /// /// /// The vault key is generated on the client and sealed to the user's own X25519 key, so the /// server cannot produce this and cannot verify that contains /// anything in particular. It stores the bytes and the signature that attributes them. /// /// /// is chosen by the client, not the server. That is what makes enrollment /// safely retryable: a client whose request timed out re-sends the identical body and gets the /// identical result, instead of accumulating a second vault it never learns about. It is also /// required by the grant signature, which covers the vault id — see docs/crypto.md §7. /// /// /// Client-generated UUIDv7 for the new vault. /// Display name. Plaintext, as all vault names are. /// /// The vault key sealed to the enrolling user's own encryption key. Opaque to the server. /// /// /// Ed25519 signature over the canonical grant tuple, by the key being enrolled. A self-grant /// carries no key log head, because there is no third party whose key could have been /// substituted. /// /// Signing timestamp, part of the signed tuple. public sealed record PersonalVaultRequest( Guid VaultId, string Name, byte[] WrappedVaultKey, byte[] GrantSignature, DateTimeOffset GrantedAt); /// A request to enroll a user's first identity key pair. /// The key statement. /// Ed25519 self-signature over the statement. /// /// An ID token whose nonce equals the SHA-256 of the statement. The server verifies it /// against the provider's JWKS. /// /// /// The user's secret bundle sealed under the passphrase-derived key. Opaque to the server. /// /// Parameters needed to re-derive the wrapping key. /// /// X25519 public key of this device, so the bundle can also be wrapped to the device and /// unlocked without re-entering the passphrase. /// /// /// The same bundle sealed to . Required whenever a device key /// is supplied: only the holder of the bundle can produce this, so a device key without its wrap /// registers a device that can never unlock anything. /// /// /// The same bundle sealed under a recovery-code-derived key. /// /// Parameters for the recovery wrap. /// /// The personal vault to create, with its key already wrapped to the enrolling user. /// public sealed record EnrollmentRequest( KeyStatement Statement, byte[] StatementSignature, string IdentityProviderToken, byte[] WrappedPrivateKey, KdfParameters KdfParameters, byte[]? DevicePublicKey, byte[]? DeviceWrappedPrivateKey, byte[]? RecoveryWrappedPrivateKey, KdfParameters? RecoveryKdfParameters, PersonalVaultRequest PersonalVault); /// Result of a successful enrollment. /// The user's identifier. /// The generation now current. /// Identity fingerprint over both public keys. /// The automatically created personal vault. /// The enrolled device, when a device key was supplied. /// /// Position of this statement in the append-only key log. Clients cache the log head and /// include it in signed operations, which is what makes a forked view detectable. /// public sealed record EnrollmentResponse( Guid UserId, int KeyGeneration, byte[] Fingerprint, Guid PersonalVaultId, Guid? DeviceId, long KeyLogSequence); /// A public-key directory entry. /// /// Before wrapping a vault key to one of these, a client must verify it: check the identity /// provider binding, compare against its pinned fingerprint, and confirm the key log head is /// consistent. Wrapping to an unverified key is the one mistake that undoes end-to-end /// encryption entirely. /// /// The user. /// Email, for display. /// Display name. /// X25519 public key. /// Ed25519 public key. /// Identity fingerprint. /// Generation of this key pair. /// Key log position of the statement that introduced it. public sealed record DirectoryEntry( Guid UserId, string? Email, string? DisplayName, byte[] EncryptionPublicKey, byte[] SigningPublicKey, byte[] Fingerprint, int KeyGeneration, long KeyLogSequence); /// The caller's own profile and unlock state. /// The user. /// OIDC issuer. /// OIDC subject. /// Email. /// Display name. /// /// True when no identity key exists yet, so the client must run enrollment before anything else. /// /// Current key generation, when enrolled. /// /// The passphrase wrap of the secret bundle, for unlocking on this device. /// /// Parameters to re-derive the wrapping key. /// Vaults the caller can reach. public sealed record MeResponse( Guid UserId, string Issuer, string Subject, string? Email, string? DisplayName, bool EnrollmentRequired, int? KeyGeneration, byte[]? WrappedPrivateKey, KdfParameters? KdfParameters, IReadOnlyList Vaults); /// A vault the caller can reach, with the wrapped key needed to open it. /// The vault. /// Display name. Plaintext, and only for vaults, not for items. /// Whether this is the caller's personal vault. /// Owning team, for a team vault. /// Current key generation. /// The caller's effective permissions, as a flags value. /// /// The vault key sealed to the caller's X25519 key. Absent when a grant is awaiting re-wrap /// after a rekey, in which case the vault is temporarily unreadable and a member holding Share /// must complete it. /// /// Whether a membership change has left this vault needing a rekey. public sealed record VaultSummary( Guid VaultId, string Name, bool IsPersonal, Guid? TeamId, uint KeyGeneration, int Permissions, byte[]? WrappedVaultKey, bool RekeyRequired);