Epsitec.Briefcases.Identity 2.8.1.2629

Prefix Reserved
dotnet add package Epsitec.Briefcases.Identity --version 2.8.1.2629
                    
NuGet\Install-Package Epsitec.Briefcases.Identity -Version 2.8.1.2629
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Epsitec.Briefcases.Identity" Version="2.8.1.2629" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Epsitec.Briefcases.Identity" Version="2.8.1.2629" />
                    
Directory.Packages.props
<PackageReference Include="Epsitec.Briefcases.Identity" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Epsitec.Briefcases.Identity --version 2.8.1.2629
                    
#r "nuget: Epsitec.Briefcases.Identity, 2.8.1.2629"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Epsitec.Briefcases.Identity@2.8.1.2629
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Epsitec.Briefcases.Identity&version=2.8.1.2629
                    
Install as a Cake Addin
#tool nuget:?package=Epsitec.Briefcases.Identity&version=2.8.1.2629
                    
Install as a Cake Tool

Epsitec.Briefcases.Identity

User identity lifecycle for Crésus Briefcases: bring a user from "nothing on this device" to a live BriefcasesSession without re-implementing key derivation, at-rest encryption, token acquisition, and keyset registration by hand. This is the single briefcases library that depends on Epsitec.Rezo.Api; the SDK stays identity-agnostic and merely consumes the BriefcasesSessionOptions this package produces.

BriefcasesIdentity owns auth and keyset concerns and produces BriefcasesSessionOptions; it never opens a BriefcasesSession itself. The host constructs the session from the returned options, so the dependency direction stays host → Identity → Sdk.

Usage — the shortest happy path

For a host that prefers one call, OpenAsync runs the whole tree (unlock, recover, or provision) and returns ready session options. It asks the host for input through IdentityPrompts.

var identity = new BriefcasesIdentity (new BriefcasesIdentityOptions
{
    AuthUrl = new Uri ("https://auth.api.cresus.ch"),
    BriefcasesServerUrl = new Uri ("https://briefcases.example.com"),
    KeyStore = new FileKeyStore (keyDirectory),
});

var options = await identity.OpenAsync (new IdentityPrompts
{
    Login          = ct => ui.AskEmailAsync (ct),         // auth login (the email)
    AuthPassword   = ct => ui.AskAuthPasswordAsync (ct),  // sent to auth
    Totp           = ct => ui.AskTotpAsync (ct),
    AtRestPassword = ct => ui.AskAtRestPasswordAsync (ct),// guards the local keys
    EnterMnemonic  = ct => ui.AskMnemonicAsync (ct),
    ShowMnemonic   = (mnemonic, ct) => ui.ShowMnemonicAsync (mnemonic, ct),
});

await using var session = new BriefcasesSession (options);
await session.ConnectAsync ();

OpenAsync authenticates first — the briefcases uid is the access token's sub claim, so it is unknown until the login (email + auth password) succeeds — then derives the uid, steps up MFA when the branch needs a keyset write, and dispatches to unlock, recover, or provision per GetStatusAsync. The local keys are always (un)locked with the at-rest password, never the auth password.

Two distinct secrets. The auth password is the account password sent to auth with the email login to get a token; the at-rest password is a briefcases-only secret that derives the AES key encrypting the local private keys. They are never the same parameter — IdentityPrompts collects them separately, and the unlock / recover / provision / publish operations only ever receive atRestPassword.

First run — provision a new identity

Provisioning generates a fresh mnemonic and key set, stores the private keys at rest, and publishes the public keyset to the auth registry. Publishing is a keyset write, so it requires an MFA-validated token: step up first, then provision. The new mnemonic comes back in the result — show it to the user once.

await using var auth = await identity.AuthenticateAsync (login, authPassword);

//  auth.Uid is the access token's `sub` claim — the briefcases uid.

await auth.StepUpMfaAsync (totpCode);   // MFA is a precondition for the write

var result = await identity.ProvisionAsync (auth, atRestPassword);

ShowToUser (result.Mnemonic);           // the only way to recover on a new device

await using var session = new BriefcasesSession (result.SessionOptions);
await session.ConnectAsync ();

ProvisionAsync enforces MFA as a client-side precondition: when the identity is not MFA-validated it throws MfaStepUpRequiredException before any network call or key store write, so the host gets one immediate, unambiguous signal.

Returning user on a known device — unlock

The returning user authenticates once (to get a fresh token and the uid from the token sub), then unlocks the local keys with the at-rest password — the registry is never contacted. The decrypted keys plus a refreshing token provider come back as session options.

await using var auth = await identity.AuthenticateAsync (login, authPassword);

if (await identity.HasLocalKeysAsync (auth.Uid))
{
    var options = await identity.UnlockAsync (auth, atRestPassword);

    await using var session = new BriefcasesSession (options);
    await session.ConnectAsync ();
}

HasLocalKeysAsync is the cheap, network-free check; it delegates to IKeyStore.ContainsAsync. UnlockAsync does not re-authenticate — it takes the already-authenticated auth — and uses only the at-rest password to decrypt the private keys.

New device — BIP-39 recovery

On a device with no local keys, recover from the BIP-39 mnemonic. Recovery re-derives the keys from (uid, mnemonic) and verifies they match the published keyset before trusting them, so a wrong mnemonic fails loudly instead of silently creating a divergent identity.

await using var auth = await identity.AuthenticateAsync (login, authPassword);

try
{
    var options = await identity.RecoverAsync (auth, mnemonic, atRestPassword);

    await using var session = new BriefcasesSession (options);
    await session.ConnectAsync ();
}
catch (IdentityMismatchException)
{
    //  Wrong mnemonic (or a keyset for another subject): ask the user to retry.
}

Recovery writes no keyset, so it needs no MFA.

Login then MFA step-up

AuthenticateAsync takes the auth login (the email) and the auth password and returns a password-tier AuthenticatedIdentity whose IsMfaValidated is false and whose Uid is the access token's sub claim. StepUpMfaAsync elevates it with a TOTP code, reusing the refresh token so the auth password is not re-entered. Both IsMfaValidated (the amr claim) and Uid (the sub claim) are read locally from the token — no server round-trip.

await using var auth = await identity.AuthenticateAsync (login, authPassword);

while (auth.IsMfaValidated == false)
{
    try
    {
        await auth.StepUpMfaAsync (await ui.AskTotpAsync ());
    }
    catch (MfaCodeInvalidException)
    {
        //  Loop and re-prompt for a fresh code.
    }
    catch (MfaRateLimitedException ex)
    {
        await ui.ShowRateLimitAsync (ex.RetryAfter);
        throw;
    }
}

Every keyset write requires an MFA-validated identity. The host only ever handles one exception type for "needs TOTP": ProvisionAsync / PublishAsync throw MfaStepUpRequiredException as a local precondition, and a server-side MFA rejection surfacing from a write is re-mapped to the same exception.

Discover a user's keys to invite them

Discovery reads another user's latest published keyset by uid, replacing the manual profile.json exchange. Reads need no MFA. A keyset outside the briefcases profile policy — ecdsa-p256-sha384, ml-dsa-65, x25519, and ml-kem-768 — is rejected as a typed KeysetProfileMismatchException rather than returning unusable keys.

await using var auth = await identity.AuthenticateAsync (login, authPassword);

UserPublicKeys inviteeKeys = await identity.GetPublicKeysAsync (auth, otherUid);

//  Feed inviteeKeys straight into the SDK invitation flow instead of importing a
//  profile.json — e.g. workspace.InviteAsync (otherUid, inviteeKeys).

Shared keyset bridge for server-side checks

BriefcasesKeysetBridge exposes the common conversion and identity checks used by both the identity flows and server-side validation code. It keeps the Epsitec.Rezo.Api dependency in this package while letting consumers work from the briefcases model types.

  • BriefcasesKeysetBridge.ComputeHkid(publicKeys) derives the auth hkid from the two signature keys in UserPublicKeys.
  • BriefcasesKeysetBridge.ToUserPublicKeys(normalizedKeyset) maps a normalized auth keyset back to the four briefcases public-key slots.
  • BriefcasesKeysetBridge.MatchesPublicKeys(normalizedKeyset, publicKeys) verifies both the hkid and the four SPKI DER public-key slots.
  • BriefcasesKeysetProfiles.Policy is the profile policy for keysets briefcases can consume: ecdsa-p256-sha384, ml-dsa-65, x25519, and ml-kem-768.

The intended server-side flow is to compute the hkid from received UserPublicKeys, read the keyset from auth by (uid, hkid), normalize it through ApiKeyset.ToNormalized(), then call MatchesPublicKeys(...). The comparison deliberately covers the encryption keys too: hkid identifies only the hybrid signing keys.

Choosing a key store

FileKeyStore writes one {uid}.keyset.json per user under a host-supplied directory, in the same EncryptedSecret layout the CLI writes, so a user provisioned by the CLI or by this package is interchangeable and the QR import/export path keeps working. DpapiKeyStore keeps the same encrypted material in a Windows DPAPI-protected file ([SupportedOSPlatform("windows")]).

IKeyStore store = OperatingSystem.IsWindows ()
    ? new DpapiKeyStore (keyDirectory)
    : new FileKeyStore (keyDirectory);

The OS protection is applied in addition to the password encryption, never instead of it: the private keys stay password-encrypted at the briefcases layer, and DPAPI adds machine/user binding on top.

Handing off to the SDK

Every flow returns a BriefcasesSessionOptions that plugs straight into the SDK:

await using var session = new BriefcasesSession (options);
await session.ConnectAsync ();

The session's AccessTokenProvider is wired to the identity's refreshing token provider, so the access token is refreshed transparently for every SignalR and blob request — the host does not re-log-in mid-session. A QR-imported identity feeds the same store-then-publish path through BriefcasesKeyImport.FromQrPayload, so provisioning from a scanned pairing code reuses, rather than duplicates, the publication flow.

Verifying workspace cards (cold-read defense in depth)

A cold card listing carries no ledger, so the SDK cannot verify the owner's envelope signature on its own. AuthWorkspaceCardSignerKeyResolver plugs the trusted auth directory into that gap (SPEC-000-024): given a card signer's id and the hkid the server serves with each card, it resolves the owner's public keys from auth so the SDK verifies the signature itself.

var resolver = new AuthWorkspaceCardSignerKeyResolver (
    authUrl, options.AccessTokenProvider);

await using var session = new BriefcasesSession (
    options, cardSignerKeyResolver: resolver);

Each WorkspaceCardInfo then carries SignatureVerified (null = not checked, true = valid, false = invalid). Verification is opt-in (no resolver ⇒ cards list unverified) and best-effort — resolution is cached per (signer, hkid), fetches the keyset in any state (a card signed by a since-rotated key still verifies), and any failure leaves the card unverified rather than failing the listing.

What the host still provides

  1. UI promptsIdentityPrompts (the email login, the auth password, TOTP, the at-rest password, mnemonic entry, mnemonic display). The package contributes no UI, and it keeps the two secrets — the auth password and the at-rest password — distinct.
  2. The key-store choiceFileKeyStore versus DpapiKeyStore, and the directory they live in.
  3. The auth endpoint configurationAuthUrl, BriefcasesServerUrl, and the token Scope on BriefcasesIdentityOptions.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.8.1.2629 0 7/15/2026
2.8.0.2629 0 7/15/2026
2.7.3.2629 29 7/14/2026
2.7.2.2629 31 7/14/2026
2.7.1.2629 35 7/14/2026
2.7.0.2629 38 7/14/2026
2.6.2.2629 38 7/14/2026
2.6.1.2629 35 7/14/2026
2.6.0.2629 42 7/14/2026
2.5.0.2628 45 7/12/2026
2.3.0.2628 58 7/10/2026
2.2.0.2628 50 7/8/2026
2.1.6.2627 52 7/3/2026
2.1.5.2627 62 7/2/2026
2.1.4.2627 56 7/2/2026
2.1.3.2627 61 6/30/2026
2.1.2.2627 55 6/29/2026
2.1.1.2627 61 6/29/2026
2.1.0.2627 55 6/29/2026
2.0.0.2626 61 6/28/2026
Loading failed