diff --git a/poetry.lock b/poetry.lock index ed5d50d..23b50ef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,143 @@ # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cryptography" +version = "44.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01"}, + {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d"}, + {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904"}, + {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44"}, + {file = "cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d"}, + {file = "cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d"}, + {file = "cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c"}, + {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f"}, + {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5"}, + {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b"}, + {file = "cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028"}, + {file = "cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06"}, + {file = "cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5"}, + {file = "cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c"}, + {file = "cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "lzallright" version = "0.2.5" @@ -19,7 +157,18 @@ files = [ {file = "lzallright-0.2.5.tar.gz", hash = "sha256:e467cb21b58669c1d2f00ba714230ec8133c302e52006038186098ffd02fafd7"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [metadata] lock-version = "2.0" -python-versions = ">=3.9" -content-hash = "78a3081397fc004fefef445735e27f724dcf1705a192c263dc34abb347372368" +python-versions = ">=3.10" +content-hash = "559d52e43a6f1d7b66c372e4ca2cedcd0d277c89ea7348c7b9f02f764838ca11" diff --git a/pyproject.toml b/pyproject.toml index 4302041..c2c8906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,9 @@ readme = "README.md" packages = [{include = "ubireader"}] [tool.poetry.dependencies] -python = ">=3.9" +python = ">=3.9.2" lzallright = "^0.2.1" +cryptography = "^44.0.2" [build-system] requires = ["poetry-core"] diff --git a/ubireader/scripts/ubireader_extract_files.py b/ubireader/scripts/ubireader_extract_files.py index a18a214..a5bc154 100755 --- a/ubireader/scripts/ubireader_extract_files.py +++ b/ubireader/scripts/ubireader_extract_files.py @@ -86,6 +86,9 @@ def main(): parser.add_argument('-o', '--output-dir', dest='outpath', help='Specify output directory path.') + parser.add_argument('-K', '--master-key', dest='master_key', + help='Master key file, given with fscryptctl e.g. to encrypt the UBIFS (support limited to fscrypt v1 policies)') + parser.add_argument('filepath', help='File to extract contents of.') if len(sys.argv) == 1: @@ -104,6 +107,19 @@ def main(): settings.uboot_fix = args.uboot_fix + if args.master_key: + path = args.master_key + if not os.path.exists(path): + parser.error("File path doesn't exist.") + else : + with open(path, "rb") as file: + if os.stat(path).st_size != 64: + parser.error("Master key file size is not 64 bytes.") + else: + master_key = file.read(64) + else: + master_key = None + if args.filepath: path = args.filepath if not os.path.exists(path): @@ -179,14 +195,14 @@ def main(): lebv_file = leb_virtual_file(ubi_obj, vol_blocks) # Extract files from UBI image. - ubifs_obj = ubifs(lebv_file) + ubifs_obj = ubifs(lebv_file, master_key=master_key) print('Extracting files to: %s' % vol_outpath) extract_files(ubifs_obj, vol_outpath, perms) elif filetype == UBIFS_NODE_MAGIC: # Create UBIFS object - ubifs_obj = ubifs(ufile_obj) + ubifs_obj = ubifs(ufile_obj, master_key=master_key) # Create directory for files. create_output_dir(outpath) diff --git a/ubireader/scripts/ubireader_list_files.py b/ubireader/scripts/ubireader_list_files.py index 1ac00be..ed65023 100755 --- a/ubireader/scripts/ubireader_list_files.py +++ b/ubireader/scripts/ubireader_list_files.py @@ -78,6 +78,9 @@ def main(): parser.add_argument('-D', '--copy-dest', dest='copyfiledest', help='Copy Destination.') + parser.add_argument('-K', '--master-key', dest='master_key', + help='Master key file, given with fscryptctl e.g. to encrypt the UBIFS (support limited to fscrypt v1 policies)') + parser.add_argument('filepath', help='UBI/UBIFS image file.') if len(sys.argv) == 1: @@ -96,6 +99,19 @@ def main(): settings.uboot_fix = args.uboot_fix + if args.master_key: + path = args.master_key + if not os.path.exists(path): + parser.error("File path doesn't exist.") + else : + with open(path, "rb") as file: + if os.stat(path).st_size != 64: + parser.error("Master key file size is not 64 bytes.") + else: + master_key = file.read(64) + else: + master_key = None + if args.filepath: path = args.filepath if not os.path.exists(path): @@ -154,7 +170,7 @@ def main(): lebv_file = leb_virtual_file(ubi_obj, vol_blocks) # Create UBIFS object. - ubifs_obj = ubifs(lebv_file) + ubifs_obj = ubifs(lebv_file, master_key=master_key) if args.listpath: list_files(ubifs_obj, args.listpath) @@ -163,7 +179,7 @@ def main(): elif filetype == UBIFS_NODE_MAGIC: # Create UBIFS object - ubifs_obj = ubifs(ufile_obj) + ubifs_obj = ubifs(ufile_obj, master_key=master_key) if args.listpath: list_files(ubifs_obj, args.listpath) diff --git a/ubireader/ubifs/__init__.py b/ubireader/ubifs/__init__.py index 3e2bbca..d8b5418 100755 --- a/ubireader/ubifs/__init__.py +++ b/ubireader/ubifs/__init__.py @@ -20,6 +20,7 @@ from ubireader.debug import error, log, verbose_display from ubireader.ubifs.defines import * from ubireader.ubifs import nodes, display +from typing import Optional class ubifs(): """UBIFS object @@ -35,9 +36,10 @@ class ubifs(): Obj:mst_node -- Master Node of UBIFS image LEB1 Obj:mst_node2 -- Master Node 2 of UBIFS image LEB2 """ - def __init__(self, ubifs_file): + def __init__(self, ubifs_file, master_key: Optional[bytes] = None): self.__name__ = 'UBIFS' self._file = ubifs_file + self.master_key = master_key try: self.file.reset() sb_chdr = nodes.common_hdr(self.file.read(UBIFS_COMMON_HDR_SZ)) diff --git a/ubireader/ubifs/decrypt.py b/ubireader/ubifs/decrypt.py new file mode 100644 index 0000000..f9cc393 --- /dev/null +++ b/ubireader/ubifs/decrypt.py @@ -0,0 +1,102 @@ +from ubireader.ubifs.defines import UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT +from ubireader.debug import error +from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes +) + +AES_BLOCK_SIZE = algorithms.AES.block_size // 8 + +def lookup_inode_nonce(inodes: dict, inode: dict) -> bytes: + # get the extended attribute 'xent' of the inode + if 'xent' not in inode or not inode['xent']: + raise ValueError(f"No xent found for inode {inode}") + + for xattr_inode in inode['xent']: + if (xattr_inode.name == UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT): + nonce_ino = inodes[xattr_inode.inum]['ino'] + nonce = nonce_ino.data[-16:] + if len(nonce) != 16: + raise ValueError(f"Invalid nonce length for inode {inode}") + return nonce + + +def derive_key_from_nonce(master_key: bytes, nonce: bytes) -> bytes: + encryptor = Cipher( + algorithms.AES(nonce), + modes.ECB(), + ).encryptor() + derived_key = encryptor.update(master_key) + encryptor.finalize() + return derived_key + + +def filename_decrypt(key: bytes, ciphertext: bytes): + + # using AES CTS-CBC mode not supported by pyca cryptography + if len(ciphertext) > AES_BLOCK_SIZE: + # Cipher Text Stealing Step + pad = AES_BLOCK_SIZE - len(ciphertext) % AES_BLOCK_SIZE + + if pad > 0: # Steal ciphertext only if needed (CTS) + decryptor = Cipher( + algorithms.AES(key[:32]), + modes.ECB(), + ).decryptor() + second_to_last = ciphertext[-2*AES_BLOCK_SIZE+pad:-AES_BLOCK_SIZE+pad] + plaintext = decryptor.update(second_to_last) + decryptor.finalize() + # Apply padding + ciphertext += plaintext[-pad:] + # Swap the last two blocks + ciphertext = ciphertext[:-2*AES_BLOCK_SIZE] + ciphertext[-AES_BLOCK_SIZE:] + ciphertext[-2*AES_BLOCK_SIZE:-AES_BLOCK_SIZE] + + # AES-CBC step + NULL_IV = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + + decryptor = Cipher( + algorithms.AES(key[:32]), + modes.CBC(NULL_IV), + ).decryptor() + plaintext = decryptor.update(ciphertext) + decryptor.finalize() + return plaintext.rstrip(b'\x00') + + +def datablock_decrypt(block_key: bytes, block_iv: bytes, block_data: bytes): + decryptor = Cipher( + algorithms.AES(block_key), + modes.XTS(block_iv), + ).decryptor() + return decryptor.update(block_data) + decryptor.finalize() + + +def decrypt_filenames(ubifs, inodes): + if ubifs.master_key is None: + for inode in inodes.values(): + for dent in inode['dent']: + dent.name = dent.raw_name.decode() + return + try: + # for every node holding a cryptographic xattr, lookup the + # nonce inode from the xattr 'inum' attr + for inode in inodes.values(): + if "dent" not in inode: + continue + nonce = lookup_inode_nonce(inodes, inode) + dec_key = derive_key_from_nonce(ubifs.master_key, nonce) + for dent in inode['dent']: + dent.name = filename_decrypt(dec_key, dent.raw_name).decode() + except Exception as e: + error(decrypt_filenames, 'Error', str(e)) + + +def decrypt_symlink_target(ubifs, inodes, dent_node) -> str: + if ubifs.master_key is None: + return inodes[dent_node.inum]['ino'].data.decode() + inode = inodes[dent_node.inum] + ino = inode['ino'] + nonce = lookup_inode_nonce(inodes, inode) + # the first two bytes is just header 0x10 0x00 all the time + # the second byte is a null byte (0x00) added, need to be removed + # before decryption + encrypted_name = ino.data[2:-1] + dec_key = derive_key_from_nonce(ubifs.master_key, nonce) + lnkname = filename_decrypt(dec_key, encrypted_name) + return lnkname.decode() diff --git a/ubireader/ubifs/defines.py b/ubireader/ubifs/defines.py index 1520706..1bc1a4d 100644 --- a/ubireader/ubifs/defines.py +++ b/ubireader/ubifs/defines.py @@ -262,6 +262,20 @@ # 'data', no size. UBIFS_INO_NODE_SZ = struct.calcsize(UBIFS_INO_NODE_FORMAT) +# Extended attribute node are identical to DENT_NODES +UBIFS_XENT_NODE_FORMAT = '<%ssQBBHI' % (UBIFS_MAX_KEY_LEN) +UBIFS_XENT_NODE_FIELDS = ['key', # Node key. + 'inum', # Target inode number. + 'padding1', # Reserved for future, zeros. + 'type', # Type of target inode. + 'nlen', # Name length. + 'cookie', # 32bit random number, used to + # construct a 64bit identifier. + ] + # 'name', no size. +UBIFS_XENT_NODE_SZ = struct.calcsize(UBIFS_XENT_NODE_FORMAT) + +UBIFS_XATTR_NAME_ENCRYPTION_CONTEXT = "c" # Directory entry node UBIFS_DENT_NODE_FORMAT = '<%ssQBBHI' % (UBIFS_MAX_KEY_LEN) @@ -282,8 +296,9 @@ UBIFS_DATA_NODE_FIELDS = ['key', # Node key. 'size', # Uncompressed data size. 'compr_type', # Compression type UBIFS_COMPR_*. - 'compr_size', # Compressed data size in bytes - # only valid when data is encrypted. + 'plaintext_size', # Compressed data size in bytes + # before encryption only valid + # when data is encrypted. ] # 'data', no size. UBIFS_DATA_NODE_SZ = struct.calcsize(UBIFS_DATA_NODE_FORMAT) diff --git a/ubireader/ubifs/list.py b/ubireader/ubifs/list.py index 166ea83..bc5a7a6 100755 --- a/ubireader/ubifs/list.py +++ b/ubireader/ubifs/list.py @@ -19,6 +19,8 @@ import os import time +import struct +from ubireader.ubifs.decrypt import lookup_inode_nonce, derive_key_from_nonce, datablock_decrypt, decrypt_symlink_target from ubireader.ubifs.defines import * from ubireader.ubifs import walk from ubireader.ubifs.misc import decompress @@ -86,7 +88,7 @@ def copy_file(ubifs, filepath, destpath): for dent in inodes[inum]['dent']: if dent.name == filename: - filedata = _process_reg_file(ubifs, inodes[dent.inum], filepath) + filedata = _process_reg_file(ubifs, inodes[dent.inum], filepath, inodes) if os.path.isdir(destpath): destpath = os.path.join(destpath, filename) with open(destpath, 'wb') as f: @@ -114,7 +116,7 @@ def print_dent(ubifs, inodes, dent_node, long=True, longts=False): lnk = "" if dent_node.type == UBIFS_ITYPE_LNK: - lnk = " -> " + inode['ino'].data.decode('utf-8') + lnk = " -> " + decrypt_symlink_target(ubifs, inodes, dent_node) if longts: mtime = inode['ino'].mtime_sec @@ -143,32 +145,47 @@ def file_leng(ubifs, inode): return 0 -def _process_reg_file(ubifs, inode, path): +def _process_reg_file(ubifs, inode, path, inodes): try: buf = bytearray() + start_key = (UBIFS_DATA_KEY << UBIFS_S_KEY_BLOCK_BITS) if 'data' in inode: compr_type = 0 sorted_data = sorted(inode['data'], key=lambda x: x.key['khash']) - last_khash = sorted_data[0].key['khash']-1 + last_khash = start_key - 1 for data in sorted_data: - + # If data nodes are missing in sequence, fill in blanks # with \x00 * UBIFS_BLOCK_SIZE if data.key['khash'] - last_khash != 1: while 1 != (data.key['khash'] - last_khash): - buf += b'\x00'*UBIFS_BLOCK_SIZE + buf += b'\x00' * UBIFS_BLOCK_SIZE last_khash += 1 compr_type = data.compr_type ubifs.file.seek(data.offset) d = ubifs.file.read(data.compr_len) + + if ubifs.master_key is not None: + nonce = lookup_inode_nonce(inodes, inode) + block_key = derive_key_from_nonce(ubifs.master_key, nonce) + # block_id is based on the current hash + # there could be empty blocks + block_id = data.key['khash']-start_key + block_iv = struct.pack(" len(buf): diff --git a/ubireader/ubifs/nodes.py b/ubireader/ubifs/nodes.py index fbf2af4..321cf45 100755 --- a/ubireader/ubifs/nodes.py +++ b/ubireader/ubifs/nodes.py @@ -80,6 +80,34 @@ def __iter__(self): def display(self, tab=''): return display.ino_node(self, tab) +class xent_node(object): + """Get xattr entry node at given LEB number + offset. + + Arguments: + Bin:buf -- Raw data to extract header information from. + + See ubifs/defines.py for object attributes. + """ + def __init__(self, buf): + fields = dict(zip(UBIFS_XENT_NODE_FIELDS, struct.unpack(UBIFS_XENT_NODE_FORMAT, buf[0:UBIFS_XENT_NODE_SZ]))) + for key in fields: + if key == 'key': + setattr(self, key, parse_key(fields[key])) + else: + setattr(self, key, fields[key]) + setattr(self, 'name', buf[-self.nlen-1:-1].decode()) + setattr(self, 'errors', []) + + def __repr__(self): + return 'UBIFS XATTR Entry Node' + + def __iter__(self): + for key in dir(self): + if not key.startswith('_'): + yield key, getattr(self, key) + + def display(self, tab=''): + return display.dent_node(self, tab) class dent_node(object): """Get dir entry node at given LEB number + offset. @@ -96,8 +124,8 @@ def __init__(self, buf): setattr(self, key, parse_key(fields[key])) else: setattr(self, key, fields[key]) - - setattr(self, 'name', '%s' % buf[-self.nlen-1:-1].decode('utf-8')) + setattr(self, 'raw_name', buf[-self.nlen-1:-1]) + setattr(self, 'name', "") setattr(self, 'errors', []) def __repr__(self): diff --git a/ubireader/ubifs/output.py b/ubireader/ubifs/output.py index b4aa653..fbfe9e7 100755 --- a/ubireader/ubifs/output.py +++ b/ubireader/ubifs/output.py @@ -20,6 +20,7 @@ import os import struct +from ubireader.ubifs.decrypt import lookup_inode_nonce, derive_key_from_nonce, datablock_decrypt, decrypt_symlink_target, decrypt_filenames from ubireader import settings from ubireader.ubifs.defines import * from ubireader.ubifs import walk @@ -92,13 +93,13 @@ def extract_dents(ubifs, inodes, dent_node, path='', perms=False): if inode['ino'].nlink > 1: if 'hlink' not in inode: inode['hlink'] = dent_path - buf = _process_reg_file(ubifs, inode, dent_path) + buf = _process_reg_file(ubifs, inode, dent_path, inodes) _write_reg_file(dent_path, buf) else: os.link(inode['hlink'], dent_path) log(extract_dents, 'Make Link: %s > %s' % (dent_path, inode['hlink'])) else: - buf = _process_reg_file(ubifs, inode, dent_path) + buf = _process_reg_file(ubifs, inode, dent_path, inodes) _write_reg_file(dent_path, buf) _set_file_timestamps(dent_path, inode) @@ -112,7 +113,9 @@ def extract_dents(ubifs, inodes, dent_node, path='', perms=False): elif dent_node.type == UBIFS_ITYPE_LNK: try: # probably will need to decompress ino data if > UBIFS_MIN_COMPR_LEN - os.symlink('%s' % inode['ino'].data.decode('utf-8'), dent_path) + inode = inodes[dent_node.inum] + lnkname = decrypt_symlink_target(ubifs, inodes, dent_node) + os.symlink('%s' % lnkname, dent_path) log(extract_dents, 'Make Symlink: %s > %s' % (dent_path, inode['ino'].data)) except Exception as e: @@ -172,10 +175,10 @@ def _write_reg_file(path, data): log(_write_reg_file, 'Make File: %s' % (path)) -def _process_reg_file(ubifs, inode, path): +def _process_reg_file(ubifs, inode, path, inodes): try: buf = bytearray() - start_key = 0x00 | (UBIFS_DATA_KEY << UBIFS_S_KEY_BLOCK_BITS) + start_key = (UBIFS_DATA_KEY << UBIFS_S_KEY_BLOCK_BITS) if 'data' in inode: compr_type = 0 sorted_data = sorted(inode['data'], key=lambda x: x.key['khash']) @@ -186,13 +189,28 @@ def _process_reg_file(ubifs, inode, path): # with \x00 * UBIFS_BLOCK_SIZE if data.key['khash'] - last_khash != 1: while 1 != (data.key['khash'] - last_khash): - buf += b'\x00'*UBIFS_BLOCK_SIZE + buf += b'\x00' * UBIFS_BLOCK_SIZE last_khash += 1 compr_type = data.compr_type ubifs.file.seek(data.offset) d = ubifs.file.read(data.compr_len) + + if ubifs.master_key is not None: + nonce = lookup_inode_nonce(inodes, inode) + block_key = derive_key_from_nonce(ubifs.master_key, nonce) + # block_id is based on the current hash + # there could be empty blocks + block_id = data.key['khash']-start_key + block_iv = struct.pack(" len(buf): buf += b'\x00' * (inode['ino'].size - len(buf)) - + return bytes(buf) diff --git a/ubireader/ubifs/walk.py b/ubireader/ubifs/walk.py index 370caf5..0b8802f 100755 --- a/ubireader/ubifs/walk.py +++ b/ubireader/ubifs/walk.py @@ -21,6 +21,7 @@ from ubireader.ubifs import nodes from ubireader.ubifs.defines import * from ubireader.debug import error, log, verbose_log, verbose_display +from ubireader.ubifs.decrypt import decrypt_filenames def index(ubifs, lnum, offset, inodes={}, bad_blocks=[]): """Walk the index gathering Inode, Dir Entry, and File nodes. @@ -36,6 +37,26 @@ def index(ubifs, lnum, offset, inodes={}, bad_blocks=[]): 'ino' -- Inode node. 'data' -- List of data nodes if present. 'dent' -- List of directory entry nodes if present. + 'xent' -- List of extended directory entry nodes if present. + """ + _index(ubifs, lnum, offset, inodes, bad_blocks) + decrypt_filenames(ubifs, inodes) + +def _index(ubifs, lnum, offset, inodes={}, bad_blocks=[]): + """Walk the index gathering Inode, Dir Entry, and File nodes. + + Arguments: + Obj:ubifs -- UBIFS object. + Int:lnum -- Logical erase block number. + Int:offset -- Offset in logical erase block. + Dict:inodes -- Dict of ino/dent/file nodes keyed to inode number. + + Returns: + Dict:inodes -- Dict of ino/dent/file nodes keyed to inode number. + 'ino' -- Inode node. + 'data' -- List of data nodes if present. + 'dent' -- List of directory entry nodes if present. + 'xent' -- List of extended directory entry nodes if present. """ if len(bad_blocks): if lnum in bad_blocks: @@ -87,7 +108,7 @@ def index(ubifs, lnum, offset, inodes={}, bad_blocks=[]): verbose_log(index, '-------------------') log(index, '%s file addr: %s' % (branch, file_offset + UBIFS_IDX_NODE_SZ + (branch_idx * UBIFS_BRANCH_SZ))) verbose_display(branch) - index(ubifs, branch.lnum, branch.offs, inodes, bad_blocks) + _index(ubifs, branch.lnum, branch.offs, inodes, bad_blocks) branch_idx += 1 elif chdr.node_type == UBIFS_INO_NODE: @@ -158,3 +179,25 @@ def index(ubifs, lnum, offset, inodes={}, bad_blocks=[]): inodes[ino_num]['dent']= [] inodes[ino_num]['dent'].append(dn) + + elif chdr.node_type == UBIFS_XENT_NODE: + try: + xn = nodes.xent_node(node_buf) + + except Exception as e: + if settings.warn_only_block_read_errors: + error(index, 'Error', 'Problem at file address: %s extracting xent_node: %s' % (file_offset, e)) + return + else: + error(index, 'Fatal', 'Problem at file address: %s extracting xent_node: %s' % (file_offset, e)) + ino_num = xn.key['ino_num'] + log(index, '%s file addr: %s, ino num: %s' % (xn, file_offset, ino_num)) + verbose_display(xn) + + if not ino_num in inodes: + inodes[ino_num] = {} + + if not 'xent' in inodes[ino_num]: + inodes[ino_num]['xent']= [] + + inodes[ino_num]['xent'].append(xn)