mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
fix: read full migration file header before detecting cipher scheme
io.ReadAtLeast can return once the smallest scheme's header is read, which truncates the larger current-scheme header when the reader delivers short reads (e.g. a network request body). Read the full header and only tolerate a short read that still covers the smallest scheme. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bc99077278
commit
5da8a84207
2 changed files with 40 additions and 2 deletions
|
|
@ -417,9 +417,14 @@ func decryptingReader(r io.Reader, password string) (io.Reader, error) {
|
|||
minHeaderSize = min(minHeaderSize, headerSize)
|
||||
}
|
||||
|
||||
// Read the full header with io.ReadFull rather than io.ReadAtLeast: the
|
||||
// reader may deliver short reads (e.g. a network request body), and
|
||||
// stopping early could truncate the header of a scheme with a larger
|
||||
// salt. A short file is only acceptable if it still covers the smallest
|
||||
// scheme header.
|
||||
header := make([]byte, maxHeaderSize)
|
||||
n, err := io.ReadAtLeast(r, header, minHeaderSize)
|
||||
if err != nil {
|
||||
n, err := io.ReadFull(r, header)
|
||||
if err != nil && !(errors.Is(err, io.ErrUnexpectedEOF) && n >= minHeaderSize) {
|
||||
return nil, fmt.Errorf("failed to read backup header: %w", err)
|
||||
}
|
||||
header = header[:n]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -187,6 +188,38 @@ func TestDecryptingReaderLegacyBackup(t *testing.T) {
|
|||
require.Equal(t, "legacy backup contents", string(dbContents))
|
||||
}
|
||||
|
||||
// TestDecryptingReaderFragmentedReader verifies that a backup file is
|
||||
// decrypted correctly even when the reader delivers one byte at a time,
|
||||
// which would truncate the header if it were not read in full.
|
||||
func TestDecryptingReaderFragmentedReader(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
cw, err := encryptingWriter(&buf, "test-unlock-password")
|
||||
require.NoError(t, err)
|
||||
|
||||
zw := zip.NewWriter(cw)
|
||||
entryWriter, err := zw.Create("nwc.db")
|
||||
require.NoError(t, err)
|
||||
_, err = entryWriter.Write([]byte("backup contents"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, zw.Close())
|
||||
|
||||
cr, err := decryptingReader(iotest.OneByteReader(bytes.NewReader(buf.Bytes())), "test-unlock-password")
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypted, err := io.ReadAll(cr)
|
||||
require.NoError(t, err)
|
||||
|
||||
zr, err := zip.NewReader(bytes.NewReader(decrypted), int64(len(decrypted)))
|
||||
require.NoError(t, err)
|
||||
|
||||
dbFile, err := zr.Open("nwc.db")
|
||||
require.NoError(t, err)
|
||||
dbContents, err := io.ReadAll(dbFile)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, dbFile.Close())
|
||||
require.Equal(t, "backup contents", string(dbContents))
|
||||
}
|
||||
|
||||
// TestDecryptingReaderWrongPassword verifies that decryption fails upfront
|
||||
// when the password does not match the backup file.
|
||||
func TestDecryptingReaderWrongPassword(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue