From 6cbe18ebbd73b7bafd8209ee732d1d99059b61a6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 16 Jan 2025 13:26:52 +0100 Subject: [PATCH] Bump securetar to 2025.1.3 (#135762) * Bump securetar to 2025.1.3 * Remove outdated fixture --- homeassistant/components/backup/manifest.json | 2 +- homeassistant/components/backup/util.py | 20 ++++++------------ homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../{ed1608a9.tar => c0cb53bd.tar} | Bin 10240 -> 10240 bytes .../backup/snapshots/test_websocket.ambr | 6 +++--- tests/components/backup/test_http.py | 10 ++++----- tests/components/backup/test_websocket.py | 6 +++--- 11 files changed, 24 insertions(+), 30 deletions(-) rename tests/components/backup/fixtures/test_backups/{ed1608a9.tar => c0cb53bd.tar} (66%) diff --git a/homeassistant/components/backup/manifest.json b/homeassistant/components/backup/manifest.json index b1b6e6f70c6..ffaed260c88 100644 --- a/homeassistant/components/backup/manifest.json +++ b/homeassistant/components/backup/manifest.json @@ -8,5 +8,5 @@ "integration_type": "system", "iot_class": "calculated", "quality_scale": "internal", - "requirements": ["cronsim==2.6", "securetar==2025.1.2"] + "requirements": ["cronsim==2.6", "securetar==2025.1.3"] } diff --git a/homeassistant/components/backup/util.py b/homeassistant/components/backup/util.py index 55f3c3c05c7..ac1525b7d69 100644 --- a/homeassistant/components/backup/util.py +++ b/homeassistant/components/backup/util.py @@ -13,13 +13,7 @@ import tarfile from typing import IO, Self, cast import aiohttp -from securetar import ( - PLAINTEXT_SIZE_HEADER, - VERSION_HEADER, - SecureTarError, - SecureTarFile, - SecureTarReadError, -) +from securetar import SecureTarError, SecureTarFile, SecureTarReadError from homeassistant.backup_restore import password_to_key from homeassistant.core import HomeAssistant @@ -205,8 +199,6 @@ def validate_password_stream( for obj in input_tar: if not obj.name.endswith((".tar", ".tgz", ".tar.gz")): continue - if obj.pax_headers.get(VERSION_HEADER) != "2.0": - raise UnsupportedSecureTarVersion istf = SecureTarFile( None, # Not used gzip=False, @@ -215,6 +207,8 @@ def validate_password_stream( fileobj=input_tar.extractfile(obj), ) with istf.decrypt(obj) as decrypted: + if istf.securetar_header.plaintext_size is None: + raise UnsupportedSecureTarVersion try: decrypted.read(1) # Read a single byte to trigger the decryption except SecureTarReadError as err: @@ -270,10 +264,6 @@ def _decrypt_backup( if not obj.name.endswith((".tar", ".tgz", ".tar.gz")): output_tar.addfile(obj, input_tar.extractfile(obj)) continue - if obj.pax_headers.get(VERSION_HEADER) != "2.0": - raise UnsupportedSecureTarVersion - decrypted_obj = copy.deepcopy(obj) - decrypted_obj.size = int(obj.pax_headers[PLAINTEXT_SIZE_HEADER]) istf = SecureTarFile( None, # Not used gzip=False, @@ -282,6 +272,10 @@ def _decrypt_backup( fileobj=input_tar.extractfile(obj), ) with istf.decrypt(obj) as decrypted: + if (plaintext_size := istf.securetar_header.plaintext_size) is None: + raise UnsupportedSecureTarVersion + decrypted_obj = copy.deepcopy(obj) + decrypted_obj.size = plaintext_size output_tar.addfile(decrypted_obj, decrypted) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6b994679780..6f7fe970cc9 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -60,7 +60,7 @@ PyTurboJPEG==1.7.5 pyudev==0.24.1 PyYAML==6.0.2 requests==2.32.3 -securetar==2025.1.2 +securetar==2025.1.3 SQLAlchemy==2.0.36 standard-aifc==3.13.0;python_version>='3.13' standard-telnetlib==3.13.0;python_version>='3.13' diff --git a/pyproject.toml b/pyproject.toml index 0bb5ad7ea8d..406fbe6cf25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ dependencies = [ "python-slugify==8.0.4", "PyYAML==6.0.2", "requests==2.32.3", - "securetar==2025.1.2", + "securetar==2025.1.3", "SQLAlchemy==2.0.36", "standard-aifc==3.13.0;python_version>='3.13'", "standard-telnetlib==3.13.0;python_version>='3.13'", diff --git a/requirements.txt b/requirements.txt index 7f12eb14274..52e1b412803 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,7 +38,7 @@ psutil-home-assistant==0.0.1 python-slugify==8.0.4 PyYAML==6.0.2 requests==2.32.3 -securetar==2025.1.2 +securetar==2025.1.3 SQLAlchemy==2.0.36 standard-aifc==3.13.0;python_version>='3.13' standard-telnetlib==3.13.0;python_version>='3.13' diff --git a/requirements_all.txt b/requirements_all.txt index 78525134731..d6d9b583d94 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2665,7 +2665,7 @@ screenlogicpy==0.10.0 scsgate==0.1.0 # homeassistant.components.backup -securetar==2025.1.2 +securetar==2025.1.3 # homeassistant.components.sendgrid sendgrid==6.8.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b2413d5ed95..4a8dfe35756 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2147,7 +2147,7 @@ sanix==1.0.6 screenlogicpy==0.10.0 # homeassistant.components.backup -securetar==2025.1.2 +securetar==2025.1.3 # homeassistant.components.emulated_kasa # homeassistant.components.sense diff --git a/tests/components/backup/fixtures/test_backups/ed1608a9.tar b/tests/components/backup/fixtures/test_backups/c0cb53bd.tar similarity index 66% rename from tests/components/backup/fixtures/test_backups/ed1608a9.tar rename to tests/components/backup/fixtures/test_backups/c0cb53bd.tar index fc928b16d1b1c3c480ea3600ef8f69d7f87dbc4c..f3b2845d5eb19b9708ae6d4fd68f7d220fb39c45 100644 GIT binary patch delta 1801 zcmZn&Xb9M#%gm8%kep;{oRqTJl-ZwA(!kh+!2kgm3=9p742>8RDkcjurc7pJ{4Zl< zp^#gWnVV{BXl`t7U}#}vVXS9iU}<1%Zp5|up!5^QNgS;bCI$?KCgvtah9;(FrY0a$ z3=Iqz6bxXd)CZ>~mlmalBo;9-AOXfjFwS0+SNSKc*+kA&ecIyfZyBD(ubI!cMm_WA z%aCQavnTFu3RPUvrQ7DzqbMwDw!>8C)#ESM_V8ct3^I=Ilzg1beC$zf#O-;#aaK!y z-(R#vdrpgc`s#i2XJr&>EIqV8=zM*hWU5WkWVb7i%S^wSKL1;sX0~f9uaUuvsw3Nl zwaY{9u^-&2w=K8pwm`h@)GUwM?#05opYAyLrY5M`T{GCoB(#3Xt%lH7*A-0KWe)aU zef(BOYM++j`g{6v*~j0wPKr=%4yes}Hfe!gpJU~-s#~vK{y*Ax=<2Gv_aV_ZjlNC4&Ey8{T&F_RMo@P{{9V;$zb4 zD^EDWGDon~`gEJXe3{acJ*~12F9{18xmj~@@GLSpee+X6_U0v*s%N@6#LM3?+|$@% zTij-`e(H(V(2ai|h8{V^_n~w*!)2?N{HwZdz5YssHI-W2*;3Th=QY z1(dX;6fD?yO4h1v@0IEAl%+eDpE>>O-^0z%*+0J1{i3?uwi|;rz?RuP}T{6Y$fqCQk;vK3tzV}tXo38)LI##?_%bG=b z<3pDP8r!Xf4&A?%e@1)LV$0`0nlv7=H*d{eu9{{wU1ZLD1K#iJd;Qw3oDcKZ*}IPM z>Dl0V`K^-KnGx5so$4zNX&ssPH`LR(xX5~A--cb8W$K&?#R>;1?;Pl?Z~y+|ZJFS$ zmljrAHOnd&3o)FFwlss-)82j2k0+&!mv!LrlRmwQeo`R&Z-HIE{g~BR902B zxFuJu7TFkfdU^6O<5vlK)(2d6Yj8}JRX+4|)~?8MjrfgA8?u%k%L|eI?(oTeWAv4` z-hTotR|p!Ht2i+fFR!rro0{ts7k2Vj5*t%;=I0FYo;`*;MXZ|ZdEQ-0;aVFVTgl*N z6|~5vs#xsS(Rz4_GR7>U43WwxX(MwiWt54bk+Cr-K|N)hB!N9a8IzKrx~n3d^sm?Z zt=tl~S{%B_!;vMb%+ zEMyXlZ(OX?@%ig1PUanRrtWi`zo+lepUNP`IHzZKWf+##`+b~yd-jftZBC2+R7ccX zcRV_D;{P=($91nJ2ycDwa_E|Y`a_P4kFk}ViGP3nJ`=uJKkI6a>(R2Dc}`sBhqkkq z$!!#$c9V7Inn{=VErV8vHj4<=7w9C%%-bL`d9~-mxvcdX2`5Wl{jO%$RbrMtklXj` zzCnz{~412t?+ufpJ$p{7Tfh2y^Or4B5KRi>o=N7d+L8zx~rQVqiqxx@`88s IL1_gZ0MYgCMgRZ+ delta 1886 zcmZn&Xb9M#%gm9QVrXVyk!ZQul-ZwA($L79!2kgm3=9p73{4pnDkcjurc7pJ{2y*? ztPo$Enp|3xT9R0#SCEsKnOBlpQ4(LAS(R#QVrao-WQMGuEVZaOGe6JPNY8-F$U-5v zBr`YF*3jJ8%+lE0(9BfN#K6$Z)ZCbBvm)C{#z`El5(Wkgh9>4FhUNz5#)hUK2N;+e zGAJ0p98kX~a^oW-2D|4RXHL(%_C7~?d38_AR9_FxM@ptShhI3_ecmS+x2j`KqCl$f zI_|x5mliuYrdf*gxIIr_AFM9Jmp-F6C4!w{=bS}eH)KyY#_#ujUVp!m^-<`C`qfU0 zjE+C&IG%1Ca;{G3nb8%Yt-??97I9tLwDjmhl_hMRRaYOz*B56U*xk5yh3dVGjW-S7 zEqSoc+lYUgqkZi0^=@VT3siOp{%K7QXboe1a$_csbo$|9;pgk?_oe(C-j^_+`lq7W54V)4JU{H`v{q1}_=;V_(L44x!nvm;Y0ooQ)f4{c z8qcMz`V|7bU3>Dx`LF9Fttez*fAjS-kC))q=bwv{Wf%RMyjiqeuJ>B8oL$SlC*}g& zzn-yQcRr?)CB>xh*sA?h`=%R=d>s7qtAztDbSs|;h`SzPcFj*bd`*A5i?g#*oY1sj zp&EyV*^7YXkrtqUjgb@E(q?9(b-nq&m-A+R z>|j`3x-|J%W9{!6RaV(YMdBM%mnkJYSt;;GV~+Xhugiq~ZSL@9;0QOCLc_SD^* zrBqk$eeN*#62tpj9FnQilA=@Viktbg{)QYYs++$!j^mDXqrTkfi;?0=`%4Qn7rfiG zQ`d+`yYZrp=jVk7vSgpxtgGiLw&BvNDAZms)$1ek#rFq$t(qU@JKVjrPI%L?jio$7?wX)>u3atlkq^y)yQr z{&7>;rJ*11>9!x-;=lN==$dvt>zzsG=Gjzjnj7KHzLhOt(~k~0>*V`eSG1h=cK712 z(u`DDWFEBt=kfg-3uGqV?tEAnV)NiC+eEJ(XWcj2deXbUi8KC@{}EPiDEvD6%AWtRY@ zs^_H}R4VNkvIf_5Ee*Rjec_Uyn>lxLdt_)^e*3mU-f+$q#US>U2@D@MR=;UGb&B`b zncsd!OIF-#bT}gMa#Ksp#$%gPV`r^9XVrc)ZFWjP)B4Xm+y=Y$pL%vSUqVmzlfT#AZs7T)c={s$rEudn0*f8o zKkwyV-l?S6ygc>Xar3bC|6i6(^_#l)czRF$vbKyFo30APo|<@(`N?CyvdFKptN$I^ zrSR~%zJj&*YN?ZD^U^nqWw%dRF0wY#{!a7JwApI5$r**qw&V#`GjSa&cq$iCqPH^Y zg!%{PsQmqpxBvNGYm*|zmGZMPB$dG{OfqxXbB&CJU(LR5_$q5?BvjD1Wy^W7hleh$ kR=e_dvh&Pc`#euyGs$UNY5LOf?yii@52fXKCMqZb04$a-DF6Tf diff --git a/tests/components/backup/snapshots/test_websocket.ambr b/tests/components/backup/snapshots/test_websocket.ambr index ac4e77fca41..41e6c574227 100644 --- a/tests/components/backup/snapshots/test_websocket.ambr +++ b/tests/components/backup/snapshots/test_websocket.ambr @@ -186,7 +186,7 @@ 'type': 'result', }) # --- -# name: test_can_decrypt_on_download[backup.local-ed1608a9-hunter2] +# name: test_can_decrypt_on_download[backup.local-c0cb53bd-hunter2] dict({ 'id': 1, 'result': None, @@ -194,7 +194,7 @@ 'type': 'result', }) # --- -# name: test_can_decrypt_on_download[backup.local-ed1608a9-wrong_password] +# name: test_can_decrypt_on_download[backup.local-c0cb53bd-wrong_password] dict({ 'error': dict({ 'code': 'password_incorrect', @@ -216,7 +216,7 @@ 'type': 'result', }) # --- -# name: test_can_decrypt_on_download[no_such_agent-ed1608a9-hunter2] +# name: test_can_decrypt_on_download[no_such_agent-c0cb53bd-hunter2] dict({ 'error': dict({ 'code': 'home_assistant_error', diff --git a/tests/components/backup/test_http.py b/tests/components/backup/test_http.py index 693434631b9..b7b86cc1d45 100644 --- a/tests/components/backup/test_http.py +++ b/tests/components/backup/test_http.py @@ -106,14 +106,14 @@ async def test_downloading_remote_encrypted_backup( hass_client: ClientSessionGenerator, ) -> None: """Test downloading a local backup file.""" - backup_path = get_fixture_path("test_backups/ed1608a9.tar", DOMAIN) + backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN) await setup_backup_integration(hass) hass.data[DATA_MANAGER].backup_agents["domain.test"] = BackupAgentTest( "test", [ AgentBackup( addons=[AddonInfo(name="Test", slug="test", version="1.0.0")], - backup_id="ed1608a9", + backup_id="c0cb53bd", database_included=True, date="1970-01-01T00:00:00Z", extra_metadata={}, @@ -141,7 +141,7 @@ async def _test_downloading_encrypted_backup( """Test downloading an encrypted backup file.""" # Try downloading without supplying a password client = await hass_client() - resp = await client.get(f"/api/backup/download/ed1608a9?agent_id={agent_id}") + resp = await client.get(f"/api/backup/download/c0cb53bd?agent_id={agent_id}") assert resp.status == 200 backup = await resp.read() # We expect a valid outer tar file, but the inner tar file is encrypted and @@ -158,7 +158,7 @@ async def _test_downloading_encrypted_backup( # Download with the wrong password resp = await client.get( - f"/api/backup/download/ed1608a9?agent_id={agent_id}&password=wrong" + f"/api/backup/download/c0cb53bd?agent_id={agent_id}&password=wrong" ) assert resp.status == 200 backup = await resp.read() @@ -171,7 +171,7 @@ async def _test_downloading_encrypted_backup( # Finally download with the correct password resp = await client.get( - f"/api/backup/download/ed1608a9?agent_id={agent_id}&password=hunter2" + f"/api/backup/download/c0cb53bd?agent_id={agent_id}&password=hunter2" ) assert resp.status == 200 backup = await resp.read() diff --git a/tests/components/backup/test_websocket.py b/tests/components/backup/test_websocket.py index 2aa6eca3b95..fe6c0c1f679 100644 --- a/tests/components/backup/test_websocket.py +++ b/tests/components/backup/test_websocket.py @@ -2560,13 +2560,13 @@ async def test_subscribe_event( ("agent_id", "backup_id", "password"), [ # Invalid agent or backup - ("no_such_agent", "ed1608a9", "hunter2"), + ("no_such_agent", "c0cb53bd", "hunter2"), ("backup.local", "no_such_backup", "hunter2"), # Legacy backup, which can't be streamed ("backup.local", "2bcb3113", "hunter2"), # New backup, which can be streamed, try with correct and wrong password - ("backup.local", "ed1608a9", "hunter2"), - ("backup.local", "ed1608a9", "wrong_password"), + ("backup.local", "c0cb53bd", "hunter2"), + ("backup.local", "c0cb53bd", "wrong_password"), ], ) @pytest.mark.usefixtures("mock_backups")