backup

package
v0.37.0 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// ManifestFileName is the sealed archive manifest: the single record
	// covering every other archive member, encrypted under the export
	// passphrase with the standalone envelope the .apb payloads use.
	ManifestFileName = "manifest.sealed"
	// ManifestSchema identifies the sealed manifest inside its envelope.
	// The schema lives in the sealed plaintext, so it also separates this
	// record from any other standalone-encrypted payload.
	ManifestSchema        = "aplane.credential-backup.manifest.v1"
	ManifestSchemaVersion = 1
)
View Source
const ReadmeContent = `# Signer Key Backup

This backup contains encrypted private keys backed up from Signer.

Backups are normally packaged as a .tar.gz archive containing this README, an
apb/ directory, and manifest.sealed. If you extracted the archive first, the
same instructions apply to the extracted directory.

manifest.sealed describes the archive: it lists every other member with its
SHA-256 digest and records the source node role. It is encrypted with the same
envelope as the .apb payloads, under the same export passphrase, so APlane can
detect a member that was removed, added, or altered after the backup was made.

## File Format

Each ` + "`.apb`" + ` file is named after the Algorand address it controls (e.g., ` + "`ABC123...XYZ.apb`" + `).

Each file is self-contained: it can be decrypted with only the file and the export passphrase (no additional metadata files are needed).

Each encrypted payload is the complete canonical managed credential record,
including durable signing metadata. Backups do not contain policy, approval
settings, network mappings, templates, endpoints, tokens, or operator config.

## Encryption Format (envelope_version 2)

Each ` + "`.apb`" + ` file is a JSON document with four fields:

- ` + "`envelope_version`" + `: Always 2 (self-contained standalone format)
- ` + "`salt`" + `: Base64-encoded 32-byte random salt for Argon2id key derivation
- ` + "`nonce`" + `: Base64-encoded 12-byte random nonce for AES-256-GCM
- ` + "`ciphertext`" + `: Base64-encoded AES-256-GCM encrypted data

### Key Derivation

The encryption key is derived using Argon2id with the following parameters:

- Time (iterations): 2
- Memory: 64 MB (65536 KiB)
- Threads: 4
- Output key length: 32 bytes (AES-256)

### Decryption Steps

1. Parse the JSON file
2. Base64-decode the ` + "`salt`" + `, ` + "`nonce`" + `, and ` + "`ciphertext`" + ` fields
3. Derive the AES-256 key: ` + "`Argon2id(passphrase, salt, time=2, memory=64MB, threads=4, keyLen=32)`" + `
4. Decrypt using AES-256-GCM with the derived key and nonce
5. The decrypted plaintext is one canonical managed credential JSON object.

## Restoring Keys

### Using apadmin (Recommended)

Import, preview, and apply through the running signer daemon:

` + "```bash" + `
apadmin backup import /path/to/this/backup.tar.gz
apadmin restore preview this-backup.tar.gz
apadmin restore apply this-backup.tar.gz
` + "```" + `

Or apply only one address:

` + "```bash" + `
apadmin restore apply this-backup.tar.gz --address <ADDRESS>
` + "```" + `

You will be prompted for the export passphrase used to encrypt the backup.
The running signer daemon encrypts restored keys into the target keystore. For
normal restore, credentials immediately operate under the destination's
current policy and configuration. Use ` + "`--replace-existing`" + ` only when
you explicitly intend to replace conflicting destination credentials. For
replacement-keystore rescue when no identity directory exists, use
` + "`apstore rebuild /path/to/this/backup.tar.gz`" + `, adding
` + "`--role sentry`" + ` when rebuilding a sentry store; rebuild verifies
that the requested destination role matches the archive's authenticated source role.

### Manual Decryption

If you need to decrypt manually, use the Argon2id and AES-256-GCM parameters documented above. Most languages have libraries for both (e.g., Python ` + "`argon2-cffi`" + ` + ` + "`cryptography`" + `, Go ` + "`golang.org/x/crypto/argon2`" + ` + ` + "`crypto/aes`" + `).

## Security Notes

- **Keep this backup secure**: These files contain your private keys
- **Remember your export passphrase**: Without it, the keys cannot be decrypted
- **Store offline**: Consider keeping backups on offline media (USB drives, etc.)
- **Multiple copies**: Keep backups in multiple secure locations

---
*Backup created by APlane*
`

ReadmeContent is the README.md content written to backup directories

Variables

This section is empty.

Functions

func ApplyCredentialEntry added in v0.35.0

func ApplyCredentialEntry(
	active storepaths.ActivePaths,
	entry CredentialEntry,
	kr *crypto.Keyring,
	replaceExisting bool,
) error

ApplyCredentialEntry writes only managed credential authority and derived public witness metadata. It never installs templates or changes key-type generation state.

func ArchiveAuthenticated added in v0.35.0

func ArchiveAuthenticated(err error) bool

ArchiveAuthenticated reports whether err proves that the archive manifest and export passphrase authenticated before a later validation failure.

func BuildManagedArchivePath

func BuildManagedArchivePath(paths storepaths.Paths, ts string) string

func CreateTarGzArchive

func CreateTarGzArchive(srcDir, destPath string) (err error)

CreateTarGzArchive packages the contents of srcDir into a tar.gz archive.

func DeleteManagedBackup

func DeleteManagedBackup(paths storepaths.Paths, archivePath string) error

func ExportAllKeys

func ExportAllKeys(paths storepaths.Paths, srcDir, destDir string, kr *crypto.Keyring, exportPassphrase []byte) (map[string]string, error)

ExportAllKeys exports all managed credential files from the keystore to a standalone backup directory. Each file is decrypted with the store's keyring and re-encrypted with the export passphrase using standalone encryption (envelope_version 2). No .keystore file is written — each backup file is self-contained.

The backup is complete or fails: silently omitting damaged authority would make the sealed archive inventory look complete when it is not.

func ExportKey

func ExportKey(paths storepaths.Paths, srcDir, destDir, address string, kr *crypto.Keyring, exportPassphrase []byte) (string, int64, error)

ExportKey exports a single key file from the keystore to a standalone backup. It decrypts the key with the store's keyring, then re-encrypts it with the export passphrase using standalone encryption (envelope_version 2). Returns the SHA256 checksum of the written key file and its size.

func ExtractTarGzArchive

func ExtractTarGzArchive(archivePath, destDir string) error

ExtractTarGzArchive extracts a tar.gz archive into destDir. Extracted directories and files are owner-private regardless of archive mode bits so validation residue is safe inside a private signer store. The archive is opened with the same no-follow regular-file enforcement as the recover path, and extraction is bounded (entry count and total decompressed size) so a crafted archive cannot exhaust the disk.

func FileSHA256

func FileSHA256(path string) (string, int64, error)

FileSHA256 returns the SHA-256 checksum and size of path.

func FormatFileSize

func FormatFileSize(bytes int64) string

FormatFileSize formats a file size in human-readable format

func IsArchivePath

func IsArchivePath(path string) bool

IsArchivePath reports whether the path looks like a supported backup archive.

func ParseBackup

func ParseBackup(decryptedJSON []byte) ([]byte, error)

ParseBackup returns the complete canonical managed credential payload. Backup bundles were an internal pre-release format and are deliberately not accepted by the first supported credential-backup contract.

func PrepareRestoreSource

func PrepareRestoreSource(source string) (string, func(), error)

PrepareRestoreSource returns an extracted directory for an archive source. The caller must invoke cleanup.

func ResolveBackupKeysDir

func ResolveBackupKeysDir(source string) string

ResolveBackupKeysDir returns the credential directory in an extracted credential-backup archive.

func ResolveManagedBackupPath

func ResolveManagedBackupPath(paths storepaths.Paths, archivePath string) (string, error)

func RestoreKeyMetadata

func RestoreKeyMetadata(keyJSON []byte) (keyType, selector string, hasLogicSigBytecode bool, err error)

RestoreKeyMetadata reads the canonical identity fields used by offline restore validation.

func ScanBackupFiles

func ScanBackupFiles(dir string) ([]string, error)

ScanBackupFiles scans a directory for .apb backup files and returns their addresses.

func StatManagedBackupArchive

func StatManagedBackupArchive(archivePath string) (os.FileInfo, error)

func WriteReadme

func WriteReadme(destDir string) error

WriteReadme writes the README.md file to the backup directory

func WriteSealedManifest added in v0.35.0

func WriteSealedManifest(
	destDir string,
	role noderole.Role,
	createdAt time.Time,
	exportPassphrase []byte,
) error

WriteSealedManifest inventories every file already staged under destDir, seals the manifest under exportPassphrase, and writes it into the archive. It must run after every other member is final: the inventory is what makes them tamper-evident.

Types

type ArchiveResult

type ArchiveResult struct {
	ArchivePath     string
	ArchiveChecksum string
	ArchiveSize     int64
	Checksums       map[string]string
	KeyCount        int
	Addresses       []string
	Verified        bool
}

func CreateKeysArchive

func CreateKeysArchive(req CreateKeysArchiveRequest) (*ArchiveResult, error)

CreateKeysArchive exports selected active keys into one tar.gz/tgz archive. When Addresses is empty, all active keys are exported.

type AuthenticatedArchiveError added in v0.35.0

type AuthenticatedArchiveError struct {
	// contains filtered or unexported fields
}

AuthenticatedArchiveError marks a restore failure that occurred only after the sealed archive manifest authenticated successfully. Callers use this to avoid treating a valid export passphrase as another passphrase guess merely because a credential or destination validation failed later.

func (*AuthenticatedArchiveError) Error added in v0.35.0

func (e *AuthenticatedArchiveError) Error() string

func (*AuthenticatedArchiveError) Unwrap added in v0.35.0

func (e *AuthenticatedArchiveError) Unwrap() error

type CreateKeysArchiveRequest added in v0.35.0

type CreateKeysArchiveRequest struct {
	Paths            storepaths.Paths
	ArchivePath      string
	Addresses        []string
	Keyring          *crypto.Keyring
	ExportPassphrase []byte
}

CreateKeysArchiveRequest contains one managed backup creation snapshot. Keyring and ExportPassphrase are borrowed for the duration of the call and are not cleared.

type CredentialEntry added in v0.35.0

type CredentialEntry struct {
	Selector string
	Category string
	KeyType  string
	KeyJSON  []byte
}

CredentialEntry is one complete canonical managed credential prepared for destination installation. KeyJSON contains private authority and must be cleared with ZeroSecrets.

func (*CredentialEntry) ZeroSecrets added in v0.35.0

func (e *CredentialEntry) ZeroSecrets()

type ManagedBackupInfo

type ManagedBackupInfo struct {
	Path      string
	FileName  string
	CreatedAt time.Time
	Size      int64
	Checksum  string
}

func ListManagedBackups

func ListManagedBackups(paths storepaths.Paths) ([]ManagedBackupInfo, error)

type Manifest added in v0.23.0

type Manifest struct {
	Schema         string `json:"schema"`
	SchemaVersion  int    `json:"schema_version"`
	SourceNodeRole string `json:"source_node_role"`
	CreatedAtUnix  int64  `json:"created_at_unix,omitempty"`

	// Members covers every archive member except the manifest itself.
	Members []ManifestMember `json:"members"`
}

Manifest is the authenticated description of a managed backup archive. Decrypting it proves the archive was created, or endorsed, by a party that knew the export passphrase (docs/PROPOSAL_ARCHIVE_MANIFEST.md); the member inventory then makes every other archive member tamper-evident.

func OpenSealedManifest added in v0.35.0

func OpenSealedManifest(sourceRoot string, exportPassphrase []byte) (Manifest, error)

OpenSealedManifest decrypts the archive's manifest with exportPassphrase and verifies the archive against it: every listed member must be present with a matching digest and size, and no unlisted member may exist. A wrong passphrase and a tampered manifest are indistinguishable by construction (GCM), exactly as for payload content.

type ManifestMember added in v0.35.0

type ManifestMember struct {
	Path   string `json:"path"` // archive-relative, forward slashes
	SHA256 string `json:"sha256"`
	Size   int64  `json:"size"`
}

ManifestMember is one archive member's authenticated identity.

type RestoreClassification added in v0.35.0

type RestoreClassification struct {
	Identical []CredentialEntry
	Pending   []CredentialEntry
	Conflicts []RestoreConflict
}

RestoreClassification is destination state pinned under the identity mutation lock immediately before generation minting.

func ClassifyRestoreSet added in v0.35.0

func ClassifyRestoreSet(
	active storepaths.ActivePaths,
	set *RestoreSet,
	kr *crypto.Keyring,
) (RestoreClassification, error)

ClassifyRestoreSet compares decoded canonical plaintext. An existing object that cannot be decrypted or parsed is a replaceable conflict, not an early error: this is what permits an explicitly authorized restore to repair a damaged credential while the signer remains recovery-blocked.

type RestoreConflict added in v0.35.0

type RestoreConflict struct {
	Selector       string
	Category       string
	KeyType        string
	ExistingSHA256 string
	Reason         string
}

RestoreConflict describes an existing destination object that differs from an incoming canonical credential or cannot be decoded for equivalence.

type RestoreError

type RestoreError struct {
	Address string
	Error   string
}

type RestoreKeyInfo

type RestoreKeyInfo struct {
	Address       string
	KeyType       string
	AlreadyExists bool
	Error         string
}

type RestoreLogger

type RestoreLogger func(format string, args ...any)

type RestorePreview

type RestorePreview struct {
	ArchivePath string
	Keys        []RestoreKeyInfo
	Errors      []RestoreError
}

func PreviewRestoreWithNodeRole added in v0.23.0

func PreviewRestoreWithNodeRole(
	paths storepaths.Paths,
	archivePath string,
	exportPassphrase []byte,
	role noderole.Role,
) (*RestorePreview, error)

type RestoreSet added in v0.35.0

type RestoreSet struct {
	ArchiveName      string
	ArchiveSHA256    string
	SourceNodeRole   string
	ArchiveCreatedAt int64
	Entries          []CredentialEntry
}

RestoreSet is a fully authenticated and payload-validated archive selection. It is intentionally process-local: direct restore does not publish a second destination-encrypted lifecycle outside the generation transaction.

func LoadManagedRestoreSet added in v0.35.0

func LoadManagedRestoreSet(
	paths storepaths.Paths,

	archivePath string,
	selectors []string,
	exportPassphrase []byte,
	role noderole.Role,
) (*RestoreSet, error)

LoadManagedRestoreSet snapshots, authenticates, decrypts, canonicalizes, and validates every selected archive credential without mutating destination storage. An empty selectors slice selects the complete archive.

func (*RestoreSet) ZeroSecrets added in v0.35.0

func (s *RestoreSet) ZeroSecrets()

type Restorer

type Restorer struct {
	Paths          storepaths.Paths
	NodeRole       noderole.Role
	Overwrite      bool
	Logf           RestoreLogger
	ActiveOverride storepaths.ActivePaths
}

Restorer is the narrow offline/rebuild adapter for credential-only backup entries. Live restores use LoadManagedRestoreSet and a generation mint.

func NewRestorer

func NewRestorer(paths storepaths.Paths) Restorer

func (Restorer) RestoreActiveForRebuild added in v0.35.0

func (r Restorer) RestoreActiveForRebuild(keysDir, selector string, kr *crypto.Keyring, exportPassphrase []byte) (string, error)

func (Restorer) RestoreKey

func (r Restorer) RestoreKey(keysDir, selector string, kr *crypto.Keyring, exportPassphrase []byte) (string, error)

func (Restorer) WithActiveNamespace added in v0.35.0

func (r Restorer) WithActiveNamespace(active storepaths.ActivePaths) Restorer

func (Restorer) WithLogger

func (r Restorer) WithLogger(logf RestoreLogger) Restorer

func (Restorer) WithNodeRole added in v0.23.0

func (r Restorer) WithNodeRole(role noderole.Role) Restorer

func (Restorer) WithOverwrite added in v0.34.0

func (r Restorer) WithOverwrite(overwrite bool) Restorer

type VerifyReport

type VerifyReport struct {
	BackupDir   string
	TotalFiles  int
	ValidFiles  int
	FailedFiles int
	Results     []VerifyResult
}

func DeepVerifyBackup

func DeepVerifyBackup(backupDir, passphrase string) (*VerifyReport, error)

func DeepVerifyBackupBytes added in v0.24.0

func DeepVerifyBackupBytes(backupDir string, passphrase []byte) (*VerifyReport, error)

DeepVerifyBackupBytes authenticates the exact archive inventory and checks that every member is a self-contained, canonical credential supported by the source node role. Verification has no network or template dependency.

type VerifyResult

type VerifyResult struct {
	Address  string
	FileName string
	Size     int64
	Valid    bool
	Error    string
	KeyType  string
}

VerifyResult contains the result of verifying one complete credential.

Jump to

Keyboard shortcuts

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