Repository: syncthing/syncthing
File: cmd/syncthing/decrypt/decrypt.go (Go)

Code before:
}

// decryptFile reads, decrypts and verifies all the blocks in src, writing
// it to dst if dst is non-nil. (If dst is nil it just becomes a
// read-and-verify operation.)
func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo, src io.ReaderAt, dst io.WriterAt) error {
	// The encrypted and plaintext files must consist of an equal number of blocks
	if len(encFi.Blocks) != len(plainFi.Blocks) {
		return fmt.Errorf("block count mismatch: encrypted %d != plaintext %d", len(encFi.Blocks), len(plainFi.Blocks))
	}

	fileKey := protocol.FileKey(plainFi.Name, c.folderKey)
	for i, encBlock := range encFi.Blocks {
		// Read the encrypted block
		buf := make([]byte, encBlock.Size)
		if _, err := src.ReadAt(buf, encBlock.Offset); err != nil {
			return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
		}

		// Decrypt it
		dec, err := protocol.DecryptBytes(buf, fileKey)
		if err != nil {
			return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
		}

		// Verify the block against the expected plaintext
		plainBlock := plainFi.Blocks[i]
		if i == len(plainFi.Blocks)-1 && len(dec) > plainBlock.Size {
			// The last block might be padded, which is fine (we skip the padding)
			dec = dec[:plainBlock.Size]
		} else if len(dec) != plainBlock.Size {
			return fmt.Errorf("plaintext block %d size mismatch, actual %d != expected %d", i, len(dec), plainBlock.Size)
		}

		// Verify the hash against the plaintext block info
		if !scanner.Validate(dec, plainBlock.Hash, 0) {
			// The block decrypted correctly but fails the hash check. This
			// is odd and unexpected, but it it's still a valid block from
			// the source. The file might have changed while we pulled it?
			err := fmt.Errorf("plaintext block %d (%d bytes) failed validation after decryption", i, plainBlock.Size)
			if c.Continue {
				log.Printf("Warning: %s: %v", plainFi.Name, err)
			} else {
				return err
			}
		}

		// Write it to the destination, unless we're just verifying.
		if dst != nil {
			if _, err := dst.WriteAt(dec, plainBlock.Offset); err != nil {
				return err

Review comment (suggestion):
```suggestion
		// Verify the block size against the expected plaintext
```

Code after:
		return fmt.Errorf("%s: %s: %w", path, plainFi.Name, err)
	} else if c.Verbose {
		log.Printf("Data verified for %q", plainFi.Name)
	}

	if plainFd != nil {
		return plainFd.Close()
	}
	return nil
}

// decryptFile reads, decrypts and verifies all the blocks in src, writing
// it to dst if dst is non-nil. (If dst is nil it just becomes a
// read-and-verify operation.)
func (c *CLI) decryptFile(encFi *protocol.FileInfo, plainFi *protocol.FileInfo, src io.ReaderAt, dst io.WriterAt) error {
	// The encrypted and plaintext files must consist of an equal number of blocks
	if len(encFi.Blocks) != len(plainFi.Blocks) {
		return fmt.Errorf("block count mismatch: encrypted %d != plaintext %d", len(encFi.Blocks), len(plainFi.Blocks))
	}

	fileKey := protocol.FileKey(plainFi.Name, c.folderKey)
	for i, encBlock := range encFi.Blocks {
		// Read the encrypted block
		buf := make([]byte, encBlock.Size)
		if _, err := src.ReadAt(buf, encBlock.Offset); err != nil {
			return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
		}

		// Decrypt it
		dec, err := protocol.DecryptBytes(buf, fileKey)
		if err != nil {
			return fmt.Errorf("encrypted block %d (%d bytes): %w", i, encBlock.Size, err)
		}

		// Verify the block size against the expected plaintext
		plainBlock := plainFi.Blocks[i]
		if i == len(plainFi.Blocks)-1 && len(dec) > plainBlock.Size {
			// The last block might be padded, which is fine (we skip the padding)
			dec = dec[:plainBlock.Size]
		} else if len(dec) != plainBlock.Size {
			return fmt.Errorf("plaintext block %d size mismatch, actual %d != expected %d", i, len(dec), plainBlock.Size)
		}

		// Verify the hash against the plaintext block info
		if !scanner.Validate(dec, plainBlock.Hash, 0) {
			// The block decrypted correctly but fails the hash check. This
			// is odd and unexpected, but it it's still a valid block from
			// the source. The file might have changed while we pulled it?
			err := fmt.Errorf("plaintext block %d (%d bytes) failed validation after decryption", i, plainBlock.Size)
			if c.Continue {
				log.Printf("Warning: %s: %s: %v", encFi.Name, plainFi.Name, err)