Parse the age identity key once and accept every identity in it (closes #165)
Restore and verify --deep now parse the configured age secret key a single time through a new helper that uses age.ParseIdentities and hands every identity to age.Decrypt. A key file with several identities (a whole age-keygen file) is fully accepted, so a blob encrypted to any of its recipients decrypts, not just the first. The helper is the first step of both commands, so a missing or unparseable key fails before anything is downloaded. Its error names the config source and never echoes the key value. config.extractAgeSecretKey and its silent fallback are removed; the key is stored raw and parsed only where decryption happens. README, the restore help, and the missing-key error now read the key from a file with \$(cat ...) rather than typed literally, keeping it out of shell history. Model: opus-4-8
This commit was merged in pull request #196.
This commit is contained in:
+18
-18
@@ -94,11 +94,10 @@ func (v *Vaultik) RunDeepVerify(snapshotID string, opts *VerifyOptions) error {
|
||||
}
|
||||
|
||||
// Parse the age secret key once, the same way restore does, and reuse
|
||||
// the identity for the database and every blob.
|
||||
identity, err := v.prepareRestoreIdentity()
|
||||
// the identities for the database and every blob.
|
||||
identities, err := v.restoreIdentities()
|
||||
if err != nil {
|
||||
return v.deepVerifyFailure(result, opts,
|
||||
fmt.Sprintf("parsing age secret key: %v", err), err)
|
||||
return v.deepVerifyFailure(result, opts, err.Error(), err)
|
||||
}
|
||||
|
||||
log.Info("Starting snapshot verification", "snapshot_id", snapshotID, "mode", "deep")
|
||||
@@ -108,7 +107,7 @@ func (v *Vaultik) RunDeepVerify(snapshotID string, opts *VerifyOptions) error {
|
||||
}
|
||||
|
||||
manifest, tempDB, dbBlobs, err := v.loadVerificationData(
|
||||
snapshotID, opts, result, identity)
|
||||
snapshotID, opts, result, identities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -129,7 +128,7 @@ func (v *Vaultik) RunDeepVerify(snapshotID string, opts *VerifyOptions) error {
|
||||
result.TotalSize = totalSize
|
||||
|
||||
err = v.runVerificationSteps(
|
||||
manifest, dbBlobs, tempDB, opts, result, totalSize, identity)
|
||||
manifest, dbBlobs, tempDB, opts, result, totalSize, identities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -154,7 +153,7 @@ func (v *Vaultik) RunDeepVerify(snapshotID string, opts *VerifyOptions) error {
|
||||
// loadVerificationData downloads manifest, database, and blob list for verification
|
||||
func (v *Vaultik) loadVerificationData(
|
||||
snapshotID string, opts *VerifyOptions, result *VerifyResult,
|
||||
identity age.Identity,
|
||||
identities []age.Identity,
|
||||
) (*snapshot.Manifest, *tempDB, []snapshot.BlobInfo, error) {
|
||||
// Resolve the identifier to the snapshot's remote key. A human ID is
|
||||
// hashed; a remote key (or its abbreviation, as printed for a
|
||||
@@ -191,7 +190,8 @@ func (v *Vaultik) loadVerificationData(
|
||||
v.stdoutf("Downloading and decrypting database...\n")
|
||||
}
|
||||
|
||||
tdb, err := v.downloadVerifiedSnapshotDB(snapshotID, remoteKey, opts, result, identity)
|
||||
tdb, err := v.downloadVerifiedSnapshotDB(
|
||||
snapshotID, remoteKey, opts, result, identities)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func (v *Vaultik) loadVerificationData(
|
||||
// identity so nothing is left on disk.
|
||||
func (v *Vaultik) downloadVerifiedSnapshotDB(
|
||||
snapshotID, remoteKey string, opts *VerifyOptions, result *VerifyResult,
|
||||
identity age.Identity,
|
||||
identities []age.Identity,
|
||||
) (*tempDB, error) {
|
||||
dbPath := fmt.Sprintf("metadata/%s/db.zst.age", remoteKey)
|
||||
log.Info("Downloading encrypted database", "path", dbPath)
|
||||
@@ -244,7 +244,7 @@ func (v *Vaultik) downloadVerifiedSnapshotDB(
|
||||
|
||||
defer func() { _ = dbReader.Close() }()
|
||||
|
||||
tdb, err := v.decryptAndLoadDatabase(dbReader, identity)
|
||||
tdb, err := v.decryptAndLoadDatabase(dbReader, identities)
|
||||
if err != nil {
|
||||
return nil, v.deepVerifyFailure(result, opts,
|
||||
fmt.Sprintf("failed to decrypt database: %v", err),
|
||||
@@ -270,7 +270,7 @@ func (v *Vaultik) runVerificationSteps(
|
||||
opts *VerifyOptions,
|
||||
result *VerifyResult,
|
||||
totalSize int64,
|
||||
identity age.Identity,
|
||||
identities []age.Identity,
|
||||
) error {
|
||||
if !opts.JSON {
|
||||
v.stdoutf("Verifying manifest against database...\n")
|
||||
@@ -297,7 +297,7 @@ func (v *Vaultik) runVerificationSteps(
|
||||
len(dbBlobs), ubytes(totalSize))
|
||||
}
|
||||
|
||||
err = v.performDeepVerificationFromDB(dbBlobs, tdb.db.Conn(), opts, identity)
|
||||
err = v.performDeepVerificationFromDB(dbBlobs, tdb.db.Conn(), opts, identities)
|
||||
if err != nil {
|
||||
return v.deepVerifyFailure(result, opts, err.Error(), err)
|
||||
}
|
||||
@@ -325,10 +325,10 @@ func (t *tempDB) Close() error {
|
||||
// from the encrypted stream. It reads through the same blobgen reader restore
|
||||
// uses, streaming the decrypted, decompressed database to a temp file.
|
||||
func (v *Vaultik) decryptAndLoadDatabase(
|
||||
reader io.ReadCloser, identity age.Identity,
|
||||
reader io.ReadCloser, identities []age.Identity,
|
||||
) (*tempDB, error) {
|
||||
// Decrypt and decompress through the shared blobgen reader.
|
||||
blobReader, err := blobgen.NewReader(reader, identity)
|
||||
blobReader, err := blobgen.NewReader(reader, identities...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create decryption reader: %w", err)
|
||||
}
|
||||
@@ -389,7 +389,7 @@ func (v *Vaultik) decryptAndLoadDatabase(
|
||||
|
||||
// verifyBlob downloads and verifies a single blob
|
||||
func (v *Vaultik) verifyBlob(
|
||||
blobInfo snapshot.BlobInfo, db *sql.DB, identity age.Identity,
|
||||
blobInfo snapshot.BlobInfo, db *sql.DB, identities []age.Identity,
|
||||
) error {
|
||||
// Download blob using shared fetch method
|
||||
reader, _, err := v.FetchBlob(v.ctx, blobInfo.Hash, blobInfo.CompressedSize)
|
||||
@@ -403,7 +403,7 @@ func (v *Vaultik) verifyBlob(
|
||||
// the plaintext as it is read. A blob's hash — its remote name — is the
|
||||
// double SHA-256 of that plaintext (see blobgen.DoubleSHA256), not of the
|
||||
// encrypted bytes.
|
||||
blobReader, err := blobgen.NewReader(reader, identity)
|
||||
blobReader, err := blobgen.NewReader(reader, identities...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create blob reader: %w", err)
|
||||
}
|
||||
@@ -687,7 +687,7 @@ func (v *Vaultik) verifyBlobExistenceFromDB(blobs []snapshot.BlobInfo) error {
|
||||
// each blob using the database as source.
|
||||
func (v *Vaultik) performDeepVerificationFromDB(
|
||||
blobs []snapshot.BlobInfo, db *sql.DB, opts *VerifyOptions,
|
||||
identity age.Identity,
|
||||
identities []age.Identity,
|
||||
) error {
|
||||
// Calculate total bytes for ETA
|
||||
var totalBytesExpected int64
|
||||
@@ -705,7 +705,7 @@ func (v *Vaultik) performDeepVerificationFromDB(
|
||||
|
||||
for i, blobInfo := range blobs {
|
||||
// Verify individual blob
|
||||
err := v.verifyBlob(blobInfo, db, identity)
|
||||
err := v.verifyBlob(blobInfo, db, identities)
|
||||
if err != nil {
|
||||
return fmt.Errorf("blob %s verification failed: %w", blobInfo.Hash, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user