Help us shape the future of Go! Take the 2026 Go Developer Survey

e2e

package
v0.44.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package e2e carries the server-side AWARENESS of client-side (E2E) encrypted folders — nothing more. The server never sees a password or a key and cannot decrypt anything; this package only lets the pipelines recognise the two artifacts the client leaves behind so they stop doing pointless (and potentially leaky) work:

  • the folder marker `.filex-e2e.json` at an encrypted folder's root (public salt + verify blob — hidden from listings, but readable via the preview endpoint so the client can unlock);
  • the `filexe2e` magic prefix every encrypted file starts with.

Design + threat model: docs/E2E-ENCRYPTION.md.

Index

Constants

View Source
const AdoptedByEnv = "env:" + EnvEscrowAdopt

AdoptedByEnv is what the record says when consent came from the env var. It names the mechanism, so a future second mechanism is legible in the data instead of being flattened into a bare "adopted".

View Source
const EnvEscrowAdopt = "FILEX_INSTALLATION_E2E_ESCROW_ADOPT"

EnvEscrowAdopt is the operator's separate, explicit consent to turn escrow on for an installation that already exists.

Why a second variable and not just "the key changed from empty, so they must have meant it": an escrow key arrives by being pasted into a compose file, a Helm values file or a .env, often copied from another deployment or from the quickstart. That is exactly the shape of an accident, and the accident is silent — the installation gains a second key holder and only new folders get it, so nothing looks wrong until somebody needs the key on an old folder and it does not work.

Why an env var and not a CLI subcommand or an admin button: the operator has to set EnvEscrowKey anyway, and that is an env var. A CLI step would mean shell access to the container plus database credentials — routine on a VPS, awkward on Kubernetes and impossible on several managed platforms filex is deployed to — and it would still leave two places that must agree. An admin button would put "give the operator a key to every folder from now on" behind an HTTP session; the person entitled to that decision is the person who can edit the environment, which is where the decision already lives. One extra line, in the file they are already editing, read once and then dead, is the smallest thing that is still a deliberate act.

View Source
const EnvEscrowKey = "FILEX_INSTALLATION_E2E_ESCROW_KEY"

EnvEscrowKey is the install-time escrow public key. Unset or empty means escrow is disabled for this installation.

View Source
const EscrowAlg = "RSA-OAEP-256"

EscrowAlg is the only escrow algorithm this version understands. It is written into every escrow slot so a future format change is legible rather than silent.

View Source
const EscrowKeyBits = 3072

EscrowKeyBits is the modulus size `filex e2e-escrow keygen` mints. 3072 buys a comfortable margin over 2048 while still generating in about a second, which matters because an operator runs it interactively.

View Source
const EscrowMinKeyBits = 2048

EscrowMinKeyBits rejects a public key too small to take seriously.

View Source
const InstallationFile = "installation.json"

InstallationFile is the operator-readable mirror inside the data dir.

View Source
const MarkerName = ".filex-e2e.json"

MarkerName is the folder marker filename dropped by the client at the root of every encrypted folder.

View Source
const PinnedByFirstBoot = "first-boot"

PinnedByFirstBoot is what the record says when escrow was there from the installation's first boot. Adoption never overwrites it — see Installation.

View Source
const SettingInstallation = "installation.pinned"

SettingInstallation is the settings-table key holding the pinned record.

Variables

View Source
var (
	ErrPlaintextIntoEncrypted = errors.New("cannot move or copy a file into an encrypted folder: the server has no key, so the file would be stored unencrypted inside it. Upload it through the web UI with the folder unlocked instead")
	ErrEncryptedOutOfFolder   = errors.New("cannot move or copy an encrypted file out of its folder: it stays encrypted, and outside the folder nothing knows which password opens it")
	ErrAcrossEncrypted        = errors.New("cannot move or copy between two different encrypted folders: each folder has its own key, and the destination's key cannot open the source's files")
	ErrNestedEncrypted        = errors.New("cannot move an encrypted folder into another encrypted folder: encrypted folders cannot be nested")
)

ErrPlaintextIntoEncrypted / ErrEncryptedOutOfFolder / ErrAcrossEncrypted are the three ways to cross the boundary. They are separate errors because the operator-facing advice differs for each.

View Source
var ErrNoEscrowKey = errors.New("e2e: no escrow key configured")

ErrNoEscrowKey is returned when no escrow key is configured at all.

View Source
var MagicPrefix = []byte("filexe2e")

MagicPrefix is the 8-byte prefix of every client-encrypted file.

Functions

func EscrowKeyID

func EscrowKeyID(spkiDER []byte) string

EscrowKeyID is the stable short name of an escrow key: the first 8 bytes of SHA-256 over the SPKI DER, hex-encoded. The browser computes the same value from the same bytes (escrowKeyId in e2ecrypto.ts).

func FindRoot

func FindRoot(ctx context.Context, lk NodeByPathLookup, storageID int64, dir string) (string, bool)

FindRoot walks dir and its ancestors (deepest first, storage root last) and returns the relative path of the nearest directory carrying an encrypted-folder marker. ok=false when no ancestor is marked. The returned root is trimmed of slashes ("" = the storage root itself).

func GenerateEscrowKeyPair

func GenerateEscrowKeyPair(bits int) (spkiB64, pkcs8B64 string, err error)

GenerateEscrowKeyPair mints a fresh escrow keypair and returns both halves base64-encoded: SPKI for the server's config, PKCS#8 for the admin's safe.

⚠ The private half is returned to the caller and never written anywhere by filex. `filex e2e-escrow keygen` prints it once; that is the only copy that will ever exist.

func GuardTransfer

func GuardTransfer(ctx context.Context, lk NodeByPathLookup, srcStorageID int64, srcRels []string, dstStorageID int64, dstRel string) error

GuardTransfer reports whether a copy/move of srcRels (relative paths on srcStorageID) into dstRel (a directory on dstStorageID) crosses an encryption boundary.

A nil lookup, or a deployment with no encrypted folders at all, costs one cheap ancestor walk per item and always returns nil.

func HasMagicPrefix

func HasMagicPrefix(b []byte) bool

HasMagicPrefix reports whether b starts with the encrypted-file magic.

func UnderEncrypted

func UnderEncrypted(ctx context.Context, lk NodeByPathLookup, storageID int64, nodePath string) bool

UnderEncrypted reports whether nodePath (a FILE or dir path, relative) sits inside an encrypted folder subtree — i.e. any of its ancestor directories carries the marker. Used by the thumb pipeline and the content extractor to skip work on ciphertext.

Types

type Adoption

type Adoption struct {
	// Allowed is the consent itself. False means empty -> set is refused
	// exactly as it was before adoption existed.
	Allowed bool
	// By is written verbatim into the record as e2e_escrow_adopted_by.
	By string
}

Adoption is the operator's explicit consent to adopt escrow, and the record of how it was given.

func ResolveAdoption

func ResolveAdoption() Adoption

ResolveAdoption reads the operator's consent from the environment. Booleans follow the same rule as the rest of filex: "1" or "true".

type EscrowKey

type EscrowKey struct {
	// KID is the first 8 bytes of SHA-256(SPKI) in hex — the short name
	// that appears in every escrow slot and in the boot banner.
	KID string
	// SPKI is the DER public key, base64, exactly as the browser imports it.
	SPKI string
	Bits int
	// contains filtered or unexported fields
}

EscrowKey is a parsed, validated installation escrow public key.

func ParseEscrowPublicKey

func ParseEscrowPublicKey(s string) (*EscrowKey, error)

ParseEscrowPublicKey validates an operator-supplied escrow public key.

Accepts base64 SPKI DER with or without PEM armour and with any whitespace, because it arrives through an env var that has been through a shell, a .env file and probably a copy-paste.

func ResolveEscrow

func ResolveEscrow() (*EscrowKey, error)

ResolveEscrow reads and validates the install-time escrow key from the environment. A missing or empty value means escrow is off. A present but unparseable value is an error — silently running without escrow when the operator believes they configured it is the one outcome to avoid.

func (*EscrowKey) SealNonce

func (k *EscrowKey) SealNonce(nonce []byte) ([]byte, error)

SealNonce encrypts a challenge nonce to the escrow public key, so that only a caller actually holding the private half can echo it back.

This is what turns "someone claims they used the escrow key" into "someone demonstrably had the escrow key in their browser". It does NOT stop an operator from decrypting files offline with the same key and never telling anyone — see docs/E2E-ENCRYPTION.md.

type Installation

type Installation struct {
	// E2EEscrowKID is the escrow key id, or "" when escrow is disabled.
	E2EEscrowKID string `json:"e2e_escrow_kid"`
	// E2EEscrowSPKI is the escrow public key, kept so the mirror file is
	// self-contained. It is public material; nothing here is a secret.
	E2EEscrowSPKI string `json:"e2e_escrow_spki,omitempty"`
	E2EEscrowAlg  string `json:"e2e_escrow_alg,omitempty"`
	// PinnedAt / PinnedBy describe the FIRST BOOT and are never rewritten,
	// not even by an adoption. "When was this installation created?" and
	// "when did it gain a second key?" are two questions, and answering the
	// second by overwriting the first would destroy the only record of the
	// installation's own age.
	PinnedAt string `json:"pinned_at,omitempty"`
	PinnedBy string `json:"pinned_by,omitempty"`
	// E2EEscrowAdoptedAt is set only when escrow was turned on AFTER the
	// first boot. Its presence is the whole answer to "was this first-boot
	// or adoption?", and its value is the boundary: folders created before
	// this instant carry no escrow slot and never will.
	E2EEscrowAdoptedAt string `json:"e2e_escrow_adopted_at,omitempty"`
	// E2EEscrowAdoptedBy records HOW consent was given (AdoptedByEnv today).
	E2EEscrowAdoptedBy string `json:"e2e_escrow_adopted_by,omitempty"`
	// E2EEscrowAdoptionNote is written alongside them so the mirror file
	// explains itself to a reader who has never seen this documentation.
	E2EEscrowAdoptionNote string `json:"e2e_escrow_adoption_note,omitempty"`
	// JustAdopted is true only on the boot that PERFORMED the adoption, so
	// that boot can say so once and loudly. It describes this process, not
	// the installation, so it is never written to the record — comparing
	// E2EEscrowAdoptedAt against the boot clock would have been close
	// enough almost always and wrong for a restart inside the same second.
	JustAdopted bool `json:"-"`
}

Installation is the pinned record. Fields are add-only: a newer filex reading an older record must see zero values, never a parse error.

func PinInstallation

func PinInstallation(ctx context.Context, store SettingsStore, dataDir string, escrow *EscrowKey, now string, adopt Adoption) (Installation, error)

PinInstallation records the install-time settings on first boot, verifies them on every boot after that, and performs the one change that is allowed: adopting escrow on an installation that already exists.

adopt carries the operator's explicit consent (see Adoption). It is consulted for exactly one transition, escrow off -> escrow on, and is ignored everywhere else — a first boot does not need consent, and no consent makes a key SWAP or a key REMOVAL safe.

Returns the effective record. An *InstallationMismatchError means the caller must abort — see server.New.

func (Installation) EscrowAdopted

func (i Installation) EscrowAdopted() bool

EscrowAdopted reports whether escrow was adopted rather than present from the first boot.

type InstallationMismatchError

type InstallationMismatchError struct {
	Setting  string
	Pinned   string
	Supplied string
	DataDir  string
	// Adoptable is true for the one transition that CAN be performed —
	// escrow off -> escrow on — when the operator has not yet said they
	// mean it. The message then explains adoption and its permanent limit
	// instead of only listing the two ways to back out.
	Adoptable bool
}

InstallationMismatchError is returned when the environment disagrees with what this installation was initialised with. Its message is the whole point: it must tell an operator what changed, why filex will not simply do as it is told, and what their actual options are.

func (*InstallationMismatchError) Error

func (e *InstallationMismatchError) Error() string

type NodeByPathLookup

type NodeByPathLookup interface {
	GetNodeByPath(ctx context.Context, storageID int64, pathHash string) (*model.Node, error)
}

NodeByPathLookup is the narrow store surface the ancestor walk needs. db.Store satisfies it.

type SettingsStore

type SettingsStore interface {
	GetSetting(ctx context.Context, key string) (string, error)
	UpsertSetting(ctx context.Context, key, value string) error
}

SettingsStore is the narrow store surface the pin needs. db.Store satisfies it.

type TransferGuardError

type TransferGuardError struct {
	Source string
	Dest   string
	Reason error
}

TransferGuardError names the item that was refused alongside the reason, so the message a user sees says which file, not just "no".

func (*TransferGuardError) Error

func (e *TransferGuardError) Error() string

func (*TransferGuardError) Unwrap

func (e *TransferGuardError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL