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

atrest

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: MIT Imports: 11 Imported by: 1

README

atrest

A small Go library that seals local files, such as token caches, with the operating system's own data protection, so a copy of the file taken off the account that wrote it cannot be read. A backup, a synced home directory and a disk image all carry the file without the means to open it.

It exists so that a program which has to keep a credential on disk does not also keep a key beside it. A key stored next to the ciphertext protects nothing, and the operating system already holds a secret tied to the user's logon that the program can borrow.

Install

go get github.com/excelano/atrest

Usage

import "github.com/excelano/atrest"

stored, err := atrest.Seal("myapp/token", cache)
if err != nil { /* ... */ }
// write stored to disk

plain, sealed, err := atrest.Open("myapp/token", stored)
switch {
case errors.Is(err, atrest.ErrLocked):
    // the key store is locked: offer atrest.Unlock(ctx), then Open again
case errors.Is(err, atrest.ErrCannotOpen):
    // sealed by another user or machine: treat the file as absent
case err != nil:
    // ...
case !sealed && atrest.Available():
    // plaintext from an older build: use it, and store it sealed now
}
if atrest.Persistent() {
    stored, err = atrest.Seal("myapp/token", cache)
} else {
    stored = cache
}

The name passed to Seal has to be passed again to Open, and data sealed under one name does not open under another. Make it specific to the program and the file, and never change it: a new name makes every existing file unreadable.

The envelope

Seal returns a JSON object naming the envelope version, the algorithm that sealed the data and, on a platform with more than one key store, the store holding the key, so that Open asks that store rather than whichever one happens to be reachable. Open accepts either that envelope or plaintext and reports which it was given, so a program can migrate its existing files on first read instead of discarding them.

The envelope is JSON so that an older build of a program, one that hands the file straight to a JSON parser, sees an object it does not recognise rather than bytes it cannot parse. A parser that tolerates unknown fields reads the envelope as an empty document and carries on, and if that older build then saves its own data back around the envelope's fields, Open recognises the mixture as plaintext and removes the stale fields. Mixed old and new builds sharing a file therefore cost a fresh start in the old build, not a failure.

Platforms

On Windows, Seal uses DPAPI, which encrypts under a key derived from the user's logon credentials, and passes the name as DPAPI's entropy.

On Linux and macOS, Seal encrypts with AES-256-GCM under a key it keeps in the platform's own secret store rather than beside the ciphertext: the user's D-Bus Secret Service on Linux, falling back to the kernel's per-user keyring when no session bus or no unlocked collection is reachable, and the login Keychain on macOS. A key that only lives in the kernel keyring does not survive a reboot, and a Secret Service whose collection is locked, as on a machine that logs in without a password to unlock it, falls through to that keyring. Persistent reports whether Seal would currently key under a store that survives a reboot. A caller whose file is costly to lose, such as a refresh token that takes an interactive sign-in to replace, can store it unsealed while Persistent is false instead of losing it at every boot.

A file sealed under the Secret Service opens only while its collection is unlocked. When it is locked again, as after a reboot on a machine whose session does not unlock it, Open reports ErrLocked, which is also an ErrCannotOpen, and Unlock asks the Secret Service to show its own prompt for the keyring's password, so a caller can offer the user that instead of discarding the file. The prompt is a window on the user's display; a process without one, such as an SSH session, is told so and the file stays closed.

Where nothing is reachable — no D-Bus session and no usable keyring, a locked Keychain, a container whose seccomp profile blocks the kernel keyring call — Seal returns its input unchanged rather than failing, so the caller's file stays plaintext and its protection is whatever file mode the caller gives it. This can happen even though Available reports true, since availability is a property of the platform and reachability is a property of the moment.

Sealing protects a file that leaves its context. It does not protect a file from other programs running as the same user, on any platform, because the operating system opens sealed data for any of them.

License

MIT.

Documentation

Overview

Package atrest protects small local files, such as token caches, with the operating system's own data-protection facility, so their contents are useless once copied off the account that wrote them: into a backup, a synced home directory, or a disk image.

Seal wraps data in a JSON envelope naming the algorithm that protected it. Open reverses Seal and also accepts plaintext, reporting which it was given, so a caller that finds plaintext written by an older build can read it and seal it again instead of discarding it. Where no facility exists, and where one exists but nothing on the machine can reach it right now, such as a container with no D-Bus session and no usable kernel keyring, Seal returns data unchanged; Available reports whether the platform has a facility at all, not whether this call will succeed.

The envelope is JSON so that an older build of a program which hands the file to a JSON parser sees an object it does not recognise, rather than bytes it cannot parse. MSAL's token cache, for one, reads an unrecognised object as an empty cache and prompts for sign-in, where unparseable bytes fail every call.

Protection is against the file leaving its context. Any process running as the same user can open what Seal produced, on every platform.

Index

Constants

This section is empty.

Variables

View Source
var ErrCannotOpen = errors.New("atrest: sealed data cannot be opened here")

ErrCannotOpen reports sealed data that this build cannot open: sealed on another machine or by another user, sealed by an algorithm this platform lacks, or written by a newer envelope version. The data is not corrupt, but nothing here will read it; a cache in this state should be treated as empty.

View Source
var ErrLocked = errors.New("atrest: the key store is locked")

ErrLocked is an ErrCannotOpen whose cause is the key store that holds the data's key being locked, or unreachable from this process, rather than the key being absent. The data opens once the store does, which Unlock can ask for, so a caller that would otherwise discard the data and start over can offer that first. errors.Is reports both ErrLocked and ErrCannotOpen for it.

Functions

func Available

func Available() bool

Available reports whether this platform has a sealing facility at all. It is true on Linux and macOS even where nothing is reachable at the moment — no D-Bus session, no usable kernel keyring, no Keychain — since that can change between one call and the next; Seal itself is what falls back to plaintext on a call it cannot seal.

func Open

func Open(name string, data []byte) (plain []byte, sealed bool, err error)

Open returns the plaintext of data and whether data was sealed. Data that is not an envelope is returned as plaintext with sealed false; a caller that can seal should then store it sealed.

A JSON object carrying envelope fields alongside others is also plaintext: an older build that preserves unknown fields read the envelope as an empty document and saved its own contents back around the envelope's fields. Those fields are removed before the plaintext is returned.

An envelope that cannot be opened here returns an error wrapping ErrCannotOpen, and also ErrLocked when unlocking its key store would open it.

func Persistent added in v0.2.0

func Persistent() bool

Persistent reports whether what Seal produces right now would still open after a reboot. It is false on Linux when only the kernel keyring is reachable, because that key does not survive one, and where the platform has no facility. A caller whose cache is worth more than the protection, such as a refresh token that costs an interactive sign-in to replace, can store it unsealed while this is false rather than lose it at every boot.

func Seal

func Seal(name string, data []byte) ([]byte, error)

Seal protects data under name and returns the envelope to store. name must be the same at every Open of the result, and should be specific to the program and the file, since it keeps one program's sealed data from opening under another's name.

func Unlock added in v0.3.0

func Unlock(ctx context.Context) error

Unlock asks the platform to unlock the key store behind an ErrLocked through the desktop's own prompt, on Linux the Secret Service's dialog for the login keyring. It returns nil once the store is unlocked, and an error when it stays locked: the prompt was dismissed, ctx ended first, this process has no display to show a prompt on, or the platform has nothing to unlock.

Types

This section is empty.

Jump to

Keyboard shortcuts

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