mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Make SecureTar v3 the default for backup creation (#166272)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,4 +34,4 @@ EXCLUDE_DATABASE_FROM_BACKUP = [
|
||||
"home-assistant_v2.db-wal",
|
||||
]
|
||||
|
||||
SECURETAR_CREATE_VERSION = 2
|
||||
SECURETAR_CREATE_VERSION = 3
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -3542,7 +3542,7 @@ async def test_initiate_backup_per_agent_encryption(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_secure_tar_archive.mock_calls[0] == call(
|
||||
ANY, ANY, "w", bufsize=4194304, create_version=2, password=inner_tar_password
|
||||
ANY, ANY, "w", bufsize=4194304, create_version=3, password=inner_tar_password
|
||||
)
|
||||
|
||||
result = await ws_client.receive_json()
|
||||
|
||||
@@ -5,10 +5,13 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterator
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tarfile
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import nacl.bindings.crypto_secretstream as nss
|
||||
import pytest
|
||||
import securetar
|
||||
|
||||
@@ -25,6 +28,32 @@ from homeassistant.core import HomeAssistant
|
||||
from tests.common import get_fixture_path
|
||||
|
||||
|
||||
def _deterministic_init_push(
|
||||
state: nss.crypto_secretstream_xchacha20poly1305_state, key: bytes
|
||||
) -> bytes:
|
||||
"""Replace init_push with init_pull + deterministic header from os.urandom.
|
||||
|
||||
libsodium's init_push generates random bytes internally, bypassing os.urandom.
|
||||
This replacement generates the header via os.urandom (which can be patched for
|
||||
deterministic tests) and uses init_pull to correctly initialize the state.
|
||||
"""
|
||||
header = os.urandom(nss.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
|
||||
nss.crypto_secretstream_xchacha20poly1305_init_pull(state, header, key)
|
||||
return header
|
||||
|
||||
|
||||
def _make_deterministic_urandom() -> callable:
|
||||
"""Create a deterministic os.urandom replacement."""
|
||||
call_idx = 0
|
||||
|
||||
def deterministic_urandom(n: int) -> bytes:
|
||||
nonlocal call_idx
|
||||
call_idx += 1
|
||||
return hashlib.sha256(f"deterministic-{call_idx}".encode()).digest()[:n]
|
||||
|
||||
return deterministic_urandom
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("backup_json_content", "expected_backup"),
|
||||
[
|
||||
@@ -432,14 +461,14 @@ async def test_decrypted_backup_streamer_wrong_password(hass: HomeAssistant) ->
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
40960, # 4 x 10240 byte of padding
|
||||
"test_backups/c0cb53bd.tar",
|
||||
"test_backups/c0cb53bd.tar.encrypted_v3",
|
||||
),
|
||||
(
|
||||
[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
],
|
||||
30720, # 3 x 10240 byte of padding
|
||||
"test_backups/c0cb53bd.tar.encrypted_skip_core2",
|
||||
"test_backups/c0cb53bd.tar.encrypted_v3_skip_core2",
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -477,16 +506,17 @@ async def test_encrypted_backup_streamer(
|
||||
async def open_backup() -> AsyncIterator[bytes]:
|
||||
return send_backup()
|
||||
|
||||
# Patch os.urandom to return values matching the nonce used in the encrypted
|
||||
# test backup. The backup has three inner tar files, but we need an extra nonce
|
||||
# for a future planned supervisor.tar.
|
||||
with patch("os.urandom") as mock_randbytes:
|
||||
mock_randbytes.side_effect = (
|
||||
bytes.fromhex("bd34ea6fc93b0614ce7af2b44b4f3957"),
|
||||
bytes.fromhex("1296d6f7554e2cb629a3dc4082bae36c"),
|
||||
bytes.fromhex("8b7a58e48faf2efb23845eb3164382e0"),
|
||||
bytes.fromhex("00000000000000000000000000000000"),
|
||||
)
|
||||
# Patch os.urandom for deterministic key derivation salts, and patch
|
||||
# crypto_secretstream init_push to use os.urandom for the stream header
|
||||
# instead of libsodium's internal CSPRNG.
|
||||
with (
|
||||
patch("os.urandom", side_effect=_make_deterministic_urandom()),
|
||||
patch(
|
||||
"nacl.bindings.crypto_secretstream"
|
||||
".crypto_secretstream_xchacha20poly1305_init_push",
|
||||
side_effect=_deterministic_init_push,
|
||||
),
|
||||
):
|
||||
encryptor = EncryptedBackupStreamer(hass, backup, open_backup, "hunter2")
|
||||
|
||||
assert encryptor.backup() == dataclasses.replace(
|
||||
@@ -587,7 +617,9 @@ async def test_encrypted_backup_streamer_random_nonce(hass: HomeAssistant) -> No
|
||||
decrypted_backup_path = get_fixture_path(
|
||||
"test_backups/c0cb53bd.tar.decrypted", DOMAIN
|
||||
)
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
encrypted_backup_path = get_fixture_path(
|
||||
"test_backups/c0cb53bd.tar.encrypted_v3", DOMAIN
|
||||
)
|
||||
backup = AgentBackup(
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
|
||||
Reference in New Issue
Block a user