From 06a5c7078f4b515f884bfa2fb98935c8bd146b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9?= Date: Sat, 17 Oct 2026 19:20:53 +0200 Subject: [PATCH] MiniABS 1.7: bundled TLS 1.2, no legacy HTTPS port needed All requests (API, covers, downloads, the streaming proxy) go through Minamp's own HTTP/TLS client (Bouncy Castle TLS 1.2, Mozilla roots, keep-alive, session resumption) via NetConnection, a drop-in HttpURLConnection, as the iPod version does with BearSSL. The phone reaches the normal https:// address directly; nothing changes on the server. - Trust: Mozilla roots + the user's pinned CAs/certificates feed the new verifier; the trust prompt, renew-safe CA pinning and forged-chain protection behave as before - PATCH is a normal request now (no raw-socket path) - Job, covers, player, downloads and proxy threads get big stacks for the handshake - embedded Roots.java and gen_roots.py replaced by assets/roots - tests: desktop shims read assets; all 104 checks pass (HTTP and HTTPS) Co-Authored-By: Claude Opus 5.5 --- AndroidManifest.xml | 4 +- README.md | 48 +- ...371dbf31afaf7ded8c4817d515f8152ed5a6-0.der | Bin 0 -> 574 bytes ...97b4e8a522a943a90d3301b19f99a8e6fcaf-0.der | Bin 0 -> 735 bytes ...d0137e724ee890170d0a0797ca406897614d-0.der | Bin 0 -> 1505 bytes ...fcf05175b60e4914344f85fcf18b3f826fd8-0.der | Bin 0 -> 1095 bytes ...4ff6887b4722adb4859609e6a925212174e0-0.der | Bin 0 -> 1452 bytes ...bf716ef774bd28f3e2cd3949cc2eaa52316a-0.der | Bin 0 -> 1400 bytes ...799a38ebd57730aa49f89be555490ab47bf8-0.der | Bin 0 -> 592 bytes ...6f28d9038bfbf04a2093c205456baf4034b2-0.der | Bin 0 -> 1551 bytes ...f884650ebfb1564d3da153a0d7f1c7c670a8-0.der | Bin 0 -> 993 bytes ...d72a82ce8c8116af6a12f0701505259f816d-0.der | Bin 0 -> 480 bytes ...66add30938e8135505ffd08b5a70c488b73b-0.der | Bin 0 -> 1476 bytes ...6f8e064b971fdddfa11523e1aee502be2b1b-0.der | Bin 0 -> 969 bytes ...24a4280ef8840064866941b494f3f30de194-0.der | Bin 0 -> 1519 bytes ...44b97081f27eb2d32757ef0242567e51fed9-0.der | Bin 0 -> 546 bytes ...8437e3d9edafce3dc9904897b48fdeb436d3-0.der | Bin 0 -> 580 bytes ...769250bbe91e2178c18a2240319eaf17e365-0.der | Bin 0 -> 1415 bytes ...75b10d1adadd3525515deb67dda91edf2154-0.der | Bin 0 -> 887 bytes ...ea4e6a11200e3949b766237385489c2e8792-0.der | Bin 0 -> 1391 bytes ...348b4c3fe39c4b2f2a1919788781b2fde745-0.der | Bin 0 -> 657 bytes ...402c7b5dbe592758be81f8b1a0c89bd9db40-0.der | Bin 0 -> 601 bytes ...c2d60f82ea189edb13b0755d9b046e67f050-0.der | Bin 0 -> 1371 bytes ...c31bde81d32ad0d8ecacd40c3da4e71aeb38-0.der | Bin 0 -> 967 bytes ...d42b3fe752585a7c1435fd71b9763f2c7bd9-0.der | Bin 0 -> 723 bytes ...ae4363646a8c15060a3b4adff12e8c1809af-0.der | Bin 0 -> 574 bytes ...a277b57e01b32d31d926b4f7b4366493d235-0.der | Bin 0 -> 531 bytes ...1f8404085341117cfe8fc657667384b14135-0.der | Bin 0 -> 1389 bytes ...f24e274d52c0b46a58590043b883b4f6b096-0.der | Bin 0 -> 922 bytes ...8b71fe1d19b65fb3f1288f23bc04595c4395-0.der | Bin 0 -> 914 bytes ...55d3c9a93b24f8b4f95a07ae048c20cea42d-0.der | Bin 0 -> 582 bytes ...5b67500e9a2e2c0cdcba7a9247f773f77269-0.der | Bin 0 -> 1380 bytes ...0249061723abd565d5bce6f9562e79c7c8e8-0.der | Bin 0 -> 953 bytes ...562253e3c442d08ec17f32fb2d58f406ace3-0.der | Bin 0 -> 1525 bytes ...aa5ea487b4f18a25daaf86f22533b7ee39ab-0.der | Bin 0 -> 1038 bytes ...b7e6a82098efcfb77957c139770ef5df885f-0.der | Bin 0 -> 1374 bytes ...557ae1206403ecc941543a54504072f2c1df-0.der | Bin 0 -> 1494 bytes ...daf39dfbf67800700df49cd84af1531b0969-0.der | Bin 0 -> 1079 bytes ...c7d2704f8539cb63fb2d863f09b26393e823-0.der | Bin 0 -> 566 bytes ...3837a2d6eb46985ae7cc638568fdaced325b-0.der | Bin 0 -> 1414 bytes ...a39453cfb9559e7f6bd8c54da53da642b714-0.der | Bin 0 -> 837 bytes ...33b9a76ad7c55cbf4dc2d4769ce2bb50d20b-0.der | Bin 0 -> 1422 bytes ...e1fad8ea116388ec542a0110b63fb5c826e6-0.der | Bin 0 -> 1421 bytes ...03ec26723b65ec4b936600e5d1af9fa80fa9-0.der | Bin 0 -> 1491 bytes ...dcebfc1a5bb0aa6923e532de48a56851f8ec-0.der | Bin 0 -> 502 bytes ...1b686c5cd3be1691995789dfc41472163d03-0.der | Bin 0 -> 1500 bytes ...b4c96c7627f44989f56d3795e701ecff86ed-0.der | Bin 0 -> 1415 bytes ...ac18333cfe8ca49f61b4f2151d005ee86ce9-0.der | Bin 0 -> 442 bytes ...c98fa750327769f72ce76ccfd9ea956526f3-0.der | Bin 0 -> 626 bytes ...4bafb45c7a8423a7fb924545dff35aab46eb-0.der | Bin 0 -> 1448 bytes ...311aae93dc3feadb1abc64d44c38494b2940-0.der | Bin 0 -> 1560 bytes ...c3357b52a21924146518f9cb7d4dbe05a82f-0.der | Bin 0 -> 1390 bytes ...af3c2bea54342640b3ff78c70768e0fdfd68-0.der | Bin 0 -> 886 bytes ...80e92c15138ab8bef8cad0f2ed793ab3697c-0.der | Bin 0 -> 594 bytes ...9c5ecfe502b0b8bdccfe3aac3b0057a13809-0.der | Bin 0 -> 1384 bytes ...ba45ab521e8115a29d695c1fc06daee1671c-0.der | Bin 0 -> 1523 bytes ...4580524922e9a5b62e238cb16cd887402dde-0.der | Bin 0 -> 1373 bytes ...88bd141d71bbcfed90e833ca29c89162f156-0.der | Bin 0 -> 1373 bytes ...bcd4a06fa193dda974a7c6cf184c1fbb4f14-0.der | Bin 0 -> 1420 bytes ...ed24cfd532db5d6e66acee1149168947a20d-0.der | Bin 0 -> 1386 bytes ...ad1e1daeeb75e7ecc9ad1ec5f590f5b98461-0.der | Bin 0 -> 1049 bytes ...d26e80718923cd527a3315639bcabc82baac-0.der | Bin 0 -> 569 bytes ...671cc5fe6cef98a1dba2ad2bdd787607c611-0.der | Bin 0 -> 1425 bytes ...dde3ff2a744eb4370369af62cd497095e486-0.der | Bin 0 -> 1355 bytes ...b9be7c5fcf0311180cf2f1e3ecb9d5e36027-0.der | Bin 0 -> 1425 bytes ...a4445e67ea7c94774e43189c3ecee4c87312-0.der | Bin 0 -> 1011 bytes ...8d24845c923b512b1cd0ac0e7a64a33b4cf0-0.der | Bin 0 -> 543 bytes ...eaae98f84c6b1703fefce5ee0bc9afed2fa6-0.der | Bin 0 -> 617 bytes ...bca2fa8dd4f6a68eaf461bf2091e9aa77576-0.der | Bin 0 -> 1380 bytes ...f59059604b03df43780a8094a649cdb975f6-0.der | Bin 0 -> 895 bytes ...a55f7c274e0d3a10562231c60a406a8d98a6-0.der | Bin 0 -> 1349 bytes ...4462a2852855675a4a4f8a790c5e904cfa5a-0.der | Bin 0 -> 1370 bytes ...2813a1d464fed7792b0db6d29e5d8308bbf9-0.der | Bin 0 -> 1127 bytes ...ccde7d8296f32c79363127c7d366c7ccf94c-0.der | Bin 0 -> 2007 bytes ...0a63fba1d7069deb8931aef39c24ea7a5c47-0.der | Bin 0 -> 1398 bytes ...f064e2c8498d97481114eeaf435fe0cd3a39-0.der | Bin 0 -> 1374 bytes ...65196c3a9fffbf0bc4bfdb20299bf0d6df8f-0.der | Bin 0 -> 525 bytes ...42c83e15138e38e53cc701da3ab52d475144-0.der | Bin 0 -> 735 bytes ...f103f952a9ba8cd636012f4adfffe82b92af-0.der | Bin 0 -> 1449 bytes ...67427bbd6f1c2a429387154ec72447e68508-0.der | Bin 0 -> 1463 bytes ...c276adf4f0fa5b7b3606db53807f3cd79d09-0.der | Bin 0 -> 600 bytes ...38b989c88f14809cb8826da96b75cadeeb8d-0.der | Bin 0 -> 559 bytes ...8011e10133cb69cdf7f710e973cf9577abbf-0.der | Bin 0 -> 525 bytes ...77b2272de78e18d5a67e5b1297dce8c5dac1-0.der | Bin 0 -> 659 bytes ...1e6fe1b4e58b38cd4664dd9184b2eead11f6-0.der | Bin 0 -> 1471 bytes ...54aeb7c096b2c72835b60f7d423e851e811a-0.der | Bin 0 -> 1431 bytes ...d918696e6d071cafa898ed1bf22538e71ab2-0.der | Bin 0 -> 537 bytes ...8ae3f7eb508c94c1bae31e7cdc3a713d4437-0.der | Bin 0 -> 1415 bytes ...0f14569371822ea32fed5faa588deb9a03a2-0.der | Bin 0 -> 1438 bytes ...0d0305d965b2bf298a6c492a3b0bea8ba25f-0.der | Bin 0 -> 1349 bytes ...806f3b0ec8c5ac7df8fd9922dcc8b15bd0bf-0.der | Bin 0 -> 967 bytes ...620226764c614af7a3e7549b6b1dac6b0f55-0.der | Bin 0 -> 920 bytes ...0b343f6c16083e2a9e9be4f9ee2de35dd3d9-0.der | Bin 0 -> 1400 bytes ...fc3960063bc493f2540de8ccc3b47e59d84a-0.der | Bin 0 -> 605 bytes ...d24c343a82ab1f0570158ad7a107762992e9-0.der | Bin 0 -> 1506 bytes ...b99d14f558ddad1db1c0c87dd6b76685ff6b-0.der | Bin 0 -> 565 bytes ...43a2a0f778fed080dff8600e49515c1d05c2-0.der | Bin 0 -> 1380 bytes ...b47d649377b28a8d0620f4ffafe3444e309c-0.der | Bin 0 -> 1386 bytes ...4cebac21908f4f278d95db510429928526b9-0.der | Bin 0 -> 959 bytes ...7dfe40104bbb8a15f44c45067d6d9ca0117a-0.der | Bin 0 -> 1453 bytes ...e898b225382efd64f9f3c37486534fa9cbda-0.der | Bin 0 -> 1380 bytes ...6391a7eefac7df3e47972cdd9c04be806e9d-0.der | Bin 0 -> 553 bytes ...43c47af5f1bc86ab36437e021703f89ccf5c-0.der | Bin 0 -> 527 bytes ...cf8b8c3b46c10d3d5c407561eabeb57f8181-0.der | Bin 0 -> 1428 bytes ...78259c056c7f870c7b62f5b043d96fab7725-0.der | Bin 0 -> 711 bytes ...ceba6df839282ba96295895cca12e56e2cd6-0.der | Bin 0 -> 1412 bytes ...f2faacfba8533a5d58a04d4f76d1fa532b0b-0.der | Bin 0 -> 507 bytes ...d890fe2720f41d7d6ac15eef76cf62cfab78-0.der | Bin 0 -> 653 bytes ...bc9d472b7e0291771923f2a5f1c1c51e57b5-0.der | Bin 0 -> 579 bytes ...da4b2284f3860c68b18b916a064a520a9728-0.der | Bin 0 -> 551 bytes ...813c6ea2e5bbca921b4b884a50f107f0cbff-0.der | Bin 0 -> 1354 bytes ...fc731db5ec5a468e4ba9bc8ab856319ceda9-0.der | Bin 0 -> 1631 bytes ...f3e3718f19d130d9cc7a2dc303161cb29cd0-0.der | Bin 0 -> 891 bytes ...2ef9470df6a3ca848f278ea48015299c54f1-0.der | Bin 0 -> 541 bytes ...867f8a28dc57b3dfe2c74facf594b917520b-0.der | Bin 0 -> 1453 bytes ...2d4a785f7dab303cd2dc815d90dee6995f09-0.der | Bin 0 -> 572 bytes ...39270d6a2019f1683191887f7f3edb1f2013-0.der | Bin 0 -> 1355 bytes ...687f2418d62a790f7592330756ea85e94707-0.der | Bin 0 -> 867 bytes ...5660b8bbc206931faf24a7161c03980f6301-0.der | Bin 0 -> 586 bytes ...f300817f2b6cd6f5d220fd224155c466d35b-0.der | Bin 0 -> 664 bytes ...0538aed47baadb39e8710440f329a43a372a-0.der | Bin 0 -> 621 bytes ...191bc63ac8ff27f29ee542bf779ce0c298a1-0.der | Bin 0 -> 1463 bytes ...e143973af986f6b33251f56e17bbee86f58c-0.der | Bin 0 -> 1446 bytes build.sh | 5 +- res/layout/activity_login.xml | 2 +- src/com/miniabs/Api.java | 78 +-- src/com/miniabs/Covers.java | 2 +- src/com/miniabs/DownloadService.java | 17 +- src/com/miniabs/Job.java | 51 +- src/com/miniabs/NetConnection.java | 177 ++++++ src/com/miniabs/PlayerService.java | 2 +- src/com/miniabs/Roots.java | 226 ------- src/com/miniabs/StreamProxy.java | 5 +- src/com/miniabs/Trust.java | 167 ++--- src/com/miniabs/Util.java | 4 +- src/com/miniabs/net/AssetRoots.java | 50 ++ src/com/miniabs/net/CertVerifier.java | 331 ++++++++++ src/com/miniabs/net/DiagLog.java | 47 ++ src/com/miniabs/net/Http.java | 588 ++++++++++++++++++ src/com/miniabs/net/Threads.java | 43 ++ test/com/miniabs/TrustTest.java | 30 +- test/run_tests.sh | 8 +- test/shim/android/content/Context.java | 2 + .../android/content/res/AssetManager.java | 10 + tools/gen_roots.py | 52 -- 145 files changed, 1400 insertions(+), 549 deletions(-) create mode 100644 assets/roots/032d371dbf31afaf7ded8c4817d515f8152ed5a6-0.der create mode 100644 assets/roots/03eb97b4e8a522a943a90d3301b19f99a8e6fcaf-0.der create mode 100644 assets/roots/0c0ed0137e724ee890170d0a0797ca406897614d-0.der create mode 100644 assets/roots/0f4efcf05175b60e4914344f85fcf18b3f826fd8-0.der create mode 100644 assets/roots/0f5b4ff6887b4722adb4859609e6a925212174e0-0.der create mode 100644 assets/roots/108abf716ef774bd28f3e2cd3949cc2eaa52316a-0.der create mode 100644 assets/roots/1336799a38ebd57730aa49f89be555490ab47bf8-0.der create mode 100644 assets/roots/14146f28d9038bfbf04a2093c205456baf4034b2-0.der create mode 100644 assets/roots/14c0f884650ebfb1564d3da153a0d7f1c7c670a8-0.der create mode 100644 assets/roots/17a7d72a82ce8c8116af6a12f0701505259f816d-0.der create mode 100644 assets/roots/1ca566add30938e8135505ffd08b5a70c488b73b-0.der create mode 100644 assets/roots/1d236f8e064b971fdddfa11523e1aee502be2b1b-0.der create mode 100644 assets/roots/20dd24a4280ef8840064866941b494f3f30de194-0.der create mode 100644 assets/roots/220f44b97081f27eb2d32757ef0242567e51fed9-0.der create mode 100644 assets/roots/229a8437e3d9edafce3dc9904897b48fdeb436d3-0.der create mode 100644 assets/roots/254d769250bbe91e2178c18a2240319eaf17e365-0.der create mode 100644 assets/roots/262175b10d1adadd3525515deb67dda91edf2154-0.der create mode 100644 assets/roots/281aea4e6a11200e3949b766237385489c2e8792-0.der create mode 100644 assets/roots/2c7a348b4c3fe39c4b2f2a1919788781b2fde745-0.der create mode 100644 assets/roots/3053402c7b5dbe592758be81f8b1a0c89bd9db40-0.der create mode 100644 assets/roots/3091c2d60f82ea189edb13b0755d9b046e67f050-0.der create mode 100644 assets/roots/31e4c31bde81d32ad0d8ecacd40c3da4e71aeb38-0.der create mode 100644 assets/roots/322ad42b3fe752585a7c1435fd71b9763f2c7bd9-0.der create mode 100644 assets/roots/3366ae4363646a8c15060a3b4adff12e8c1809af-0.der create mode 100644 assets/roots/3582a277b57e01b32d31d926b4f7b4366493d235-0.der create mode 100644 assets/roots/36341f8404085341117cfe8fc657667384b14135-0.der create mode 100644 assets/roots/369df24e274d52c0b46a58590043b883b4f6b096-0.der create mode 100644 assets/roots/39d28b71fe1d19b65fb3f1288f23bc04595c4395-0.der create mode 100644 assets/roots/3f1855d3c9a93b24f8b4f95a07ae048c20cea42d-0.der create mode 100644 assets/roots/41655b67500e9a2e2c0cdcba7a9247f773f77269-0.der create mode 100644 assets/roots/41fc0249061723abd565d5bce6f9562e79c7c8e8-0.der create mode 100644 assets/roots/4264562253e3c442d08ec17f32fb2d58f406ace3-0.der create mode 100644 assets/roots/42aaaa5ea487b4f18a25daaf86f22533b7ee39ab-0.der create mode 100644 assets/roots/42d0b7e6a82098efcfb77957c139770ef5df885f-0.der create mode 100644 assets/roots/481f557ae1206403ecc941543a54504072f2c1df-0.der create mode 100644 assets/roots/4a7cdaf39dfbf67800700df49cd84af1531b0969-0.der create mode 100644 assets/roots/4b1ec7d2704f8539cb63fb2d863f09b26393e823-0.der create mode 100644 assets/roots/4e9b3837a2d6eb46985ae7cc638568fdaced325b-0.der create mode 100644 assets/roots/4f59a39453cfb9559e7f6bd8c54da53da642b714-0.der create mode 100644 assets/roots/538933b9a76ad7c55cbf4dc2d4769ce2bb50d20b-0.der create mode 100644 assets/roots/5588e1fad8ea116388ec542a0110b63fb5c826e6-0.der create mode 100644 assets/roots/55f003ec26723b65ec4b936600e5d1af9fa80fa9-0.der create mode 100644 assets/roots/5cd9dcebfc1a5bb0aa6923e532de48a56851f8ec-0.der create mode 100644 assets/roots/5e021b686c5cd3be1691995789dfc41472163d03-0.der create mode 100644 assets/roots/62fbb4c96c7627f44989f56d3795e701ecff86ed-0.der create mode 100644 assets/roots/6435ac18333cfe8ca49f61b4f2151d005ee86ce9-0.der create mode 100644 assets/roots/64f5c98fa750327769f72ce76ccfd9ea956526f3-0.der create mode 100644 assets/roots/69ad4bafb45c7a8423a7fb924545dff35aab46eb-0.der create mode 100644 assets/roots/6cf8311aae93dc3feadb1abc64d44c38494b2940-0.der create mode 100644 assets/roots/6f43c3357b52a21924146518f9cb7d4dbe05a82f-0.der create mode 100644 assets/roots/7036af3c2bea54342640b3ff78c70768e0fdfd68-0.der create mode 100644 assets/roots/704b80e92c15138ab8bef8cad0f2ed793ab3697c-0.der create mode 100644 assets/roots/71719c5ecfe502b0b8bdccfe3aac3b0057a13809-0.der create mode 100644 assets/roots/724dba45ab521e8115a29d695c1fc06daee1671c-0.der create mode 100644 assets/roots/72c74580524922e9a5b62e238cb16cd887402dde-0.der create mode 100644 assets/roots/79e688bd141d71bbcfed90e833ca29c89162f156-0.der create mode 100644 assets/roots/79eabcd4a06fa193dda974a7c6cf184c1fbb4f14-0.der create mode 100644 assets/roots/7e63ed24cfd532db5d6e66acee1149168947a20d-0.der create mode 100644 assets/roots/7f7fad1e1daeeb75e7ecc9ad1ec5f590f5b98461-0.der create mode 100644 assets/roots/81abd26e80718923cd527a3315639bcabc82baac-0.der create mode 100644 assets/roots/83cf671cc5fe6cef98a1dba2ad2bdd787607c611-0.der create mode 100644 assets/roots/83fcdde3ff2a744eb4370369af62cd497095e486-0.der create mode 100644 assets/roots/843eb9be7c5fcf0311180cf2f1e3ecb9d5e36027-0.der create mode 100644 assets/roots/887da4445e67ea7c94774e43189c3ecee4c87312-0.der create mode 100644 assets/roots/8a938d24845c923b512b1cd0ac0e7a64a33b4cf0-0.der create mode 100644 assets/roots/8aa2eaae98f84c6b1703fefce5ee0bc9afed2fa6-0.der create mode 100644 assets/roots/8df6bca2fa8dd4f6a68eaf461bf2091e9aa77576-0.der create mode 100644 assets/roots/8e88f59059604b03df43780a8094a649cdb975f6-0.der create mode 100644 assets/roots/9370a55f7c274e0d3a10562231c60a406a8d98a6-0.der create mode 100644 assets/roots/953e4462a2852855675a4a4f8a790c5e904cfa5a-0.der create mode 100644 assets/roots/96db2813a1d464fed7792b0db6d29e5d8308bbf9-0.der create mode 100644 assets/roots/9717ccde7d8296f32c79363127c7d366c7ccf94c-0.der create mode 100644 assets/roots/984a0a63fba1d7069deb8931aef39c24ea7a5c47-0.der create mode 100644 assets/roots/9b55f064e2c8498d97481114eeaf435fe0cd3a39-0.der create mode 100644 assets/roots/9bbd65196c3a9fffbf0bc4bfdb20299bf0d6df8f-0.der create mode 100644 assets/roots/9be242c83e15138e38e53cc701da3ab52d475144-0.der create mode 100644 assets/roots/a27cf103f952a9ba8cd636012f4adfffe82b92af-0.der create mode 100644 assets/roots/a4c867427bbd6f1c2a429387154ec72447e68508-0.der create mode 100644 assets/roots/a4d7c276adf4f0fa5b7b3606db53807f3cd79d09-0.der create mode 100644 assets/roots/a76e38b989c88f14809cb8826da96b75cadeeb8d-0.der create mode 100644 assets/roots/aab38011e10133cb69cdf7f710e973cf9577abbf-0.der create mode 100644 assets/roots/abf377b2272de78e18d5a67e5b1297dce8c5dac1-0.der create mode 100644 assets/roots/b0e31e6fe1b4e58b38cd4664dd9184b2eead11f6-0.der create mode 100644 assets/roots/b63a54aeb7c096b2c72835b60f7d423e851e811a-0.der create mode 100644 assets/roots/b7e7d918696e6d071cafa898ed1bf22538e71ab2-0.der create mode 100644 assets/roots/badf8ae3f7eb508c94c1bae31e7cdc3a713d4437-0.der create mode 100644 assets/roots/bece0f14569371822ea32fed5faa588deb9a03a2-0.der create mode 100644 assets/roots/c17e0d0305d965b2bf298a6c492a3b0bea8ba25f-0.der create mode 100644 assets/roots/c5c7806f3b0ec8c5ac7df8fd9922dcc8b15bd0bf-0.der create mode 100644 assets/roots/c90a620226764c614af7a3e7549b6b1dac6b0f55-0.der create mode 100644 assets/roots/c9770b343f6c16083e2a9e9be4f9ee2de35dd3d9-0.der create mode 100644 assets/roots/cc74fc3960063bc493f2540de8ccc3b47e59d84a-0.der create mode 100644 assets/roots/cd30d24c343a82ab1f0570158ad7a107762992e9-0.der create mode 100644 assets/roots/cf3ab99d14f558ddad1db1c0c87dd6b76685ff6b-0.der create mode 100644 assets/roots/d07c43a2a0f778fed080dff8600e49515c1d05c2-0.der create mode 100644 assets/roots/d422b47d649377b28a8d0620f4ffafe3444e309c-0.der create mode 100644 assets/roots/d83a4cebac21908f4f278d95db510429928526b9-0.der create mode 100644 assets/roots/db967dfe40104bbb8a15f44c45067d6d9ca0117a-0.der create mode 100644 assets/roots/dd8ae898b225382efd64f9f3c37486534fa9cbda-0.der create mode 100644 assets/roots/ddb36391a7eefac7df3e47972cdd9c04be806e9d-0.der create mode 100644 assets/roots/de1d43c47af5f1bc86ab36437e021703f89ccf5c-0.der create mode 100644 assets/roots/df22cf8b8c3b46c10d3d5c407561eabeb57f8181-0.der create mode 100644 assets/roots/e24e78259c056c7f870c7b62f5b043d96fab7725-0.der create mode 100644 assets/roots/e7e9ceba6df839282ba96295895cca12e56e2cd6-0.der create mode 100644 assets/roots/e9c6f2faacfba8533a5d58a04d4f76d1fa532b0b-0.der create mode 100644 assets/roots/eb25d890fe2720f41d7d6ac15eef76cf62cfab78-0.der create mode 100644 assets/roots/eb8ebc9d472b7e0291771923f2a5f1c1c51e57b5-0.der create mode 100644 assets/roots/ef58da4b2284f3860c68b18b916a064a520a9728-0.der create mode 100644 assets/roots/f026813c6ea2e5bbca921b4b884a50f107f0cbff-0.der create mode 100644 assets/roots/f180fc731db5ec5a468e4ba9bc8ab856319ceda9-0.der create mode 100644 assets/roots/f20ef3e3718f19d130d9cc7a2dc303161cb29cd0-0.der create mode 100644 assets/roots/f33f2ef9470df6a3ca848f278ea48015299c54f1-0.der create mode 100644 assets/roots/f4ee867f8a28dc57b3dfe2c74facf594b917520b-0.der create mode 100644 assets/roots/f5042d4a785f7dab303cd2dc815d90dee6995f09-0.der create mode 100644 assets/roots/f59639270d6a2019f1683191887f7f3edb1f2013-0.der create mode 100644 assets/roots/f59c687f2418d62a790f7592330756ea85e94707-0.der create mode 100644 assets/roots/f7025660b8bbc206931faf24a7161c03980f6301-0.der create mode 100644 assets/roots/f85cf300817f2b6cd6f5d220fd224155c466d35b-0.der create mode 100644 assets/roots/f9780538aed47baadb39e8710440f329a43a372a-0.der create mode 100644 assets/roots/fbb2191bc63ac8ff27f29ee542bf779ce0c298a1-0.der create mode 100644 assets/roots/fcc3e143973af986f6b33251f56e17bbee86f58c-0.der create mode 100644 src/com/miniabs/NetConnection.java delete mode 100644 src/com/miniabs/Roots.java create mode 100644 src/com/miniabs/net/AssetRoots.java create mode 100644 src/com/miniabs/net/CertVerifier.java create mode 100644 src/com/miniabs/net/DiagLog.java create mode 100644 src/com/miniabs/net/Http.java create mode 100644 src/com/miniabs/net/Threads.java create mode 100644 test/shim/android/content/res/AssetManager.java delete mode 100644 tools/gen_roots.py diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c4d0dec..4ec0892 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/README.md b/README.md index ceaeb17..8b2c6d2 100644 --- a/README.md +++ b/README.md @@ -16,51 +16,21 @@ Sign in with your username and password, or tick "Use an API key instead". It wo ## HTTPS (from outside your home) -Android 2.3 only speaks **TLS 1.0**, which modern servers refuse. So HTTPS needs one change on the server and is handled by the app from there. - -### Server: a legacy HTTPS port - -Add a second `server` block to nginx, on its own port, used only by the phone. Your normal HTTPS site stays strict. - -```nginx -server { - listen 8443 ssl; - server_name abs.example.com; - ssl_certificate /etc/letsencrypt/live/abs.example.com/fullchain.pem; # must be an RSA certificate - ssl_certificate_key /etc/letsencrypt/live/abs.example.com/privkey.pem; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - # @SECLEVEL=0 is required: OpenSSL 3 refuses TLS 1.0 without it - ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA:AES128-SHA:@SECLEVEL=0"; - ssl_prefer_server_ciphers on; - client_max_body_size 0; - location / { - proxy_pass http://127.0.0.1:13378; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_http_version 1.1; - proxy_buffering off; - } -} -``` - -Then sign in with `https://abs.example.com:8443`. - -- **RSA certificate:** Android 2.3 can't use ECDSA certificates. With certbot, request one with `--key-type rsa`. -- **Security:** TLS 1.0 with these ciphers is weak by today's standards, but much better than plain HTTP over the internet. Only open this port if you need it. -- **Caddy** can't serve TLS 1.0; put nginx (or HAProxy) in front for this port. +Android 2.3 only speaks TLS 1.0, which modern servers refuse, so since 1.7 MiniABS brings its own +**TLS 1.2** (Bouncy Castle, the same client as Minamp, with Mozilla's root certificates, +keep-alive and session resumption). Sign in with your server's normal `https://` address, at +home or away; nothing changes on the server. (A legacy TLS 1.0 port set up for older versions +keeps working too.) ### App: certificates -- **Built-in roots:** MiniABS trusts what the phone trusts, plus modern root certificates the phone's 2011-era list lacks: Let's Encrypt, Google Trust Services, Sectigo/ZeroSSL, DigiCert G2, Amazon and others. Most certificates just work. +- **Built-in roots:** MiniABS checks certificates against Mozilla's current root list (`assets/roots`), so Let's Encrypt, Google Trust Services, Sectigo/ZeroSSL and the rest just work. - **Anything else** (self-signed, a private CA, a new root): MiniABS shows the certificate's SHA-256 fingerprint and asks once. Compare it with the fingerprint your computer's browser shows for the site before tapping Trust. It then trusts the CA that issued it, so renewals keep working. - **Settings → Certificates you trusted** lets you forget them. ### App: streaming -Android 2.x's media player can't stream https:// URLs, so for HTTPS servers MiniABS runs a small proxy on the phone (127.0.0.1 only, random unguessable URLs). It forwards seeks and gets a fresh sign-in token for each request, so long episodes don't break when the token expires. +Android 2.x's media player can't stream modern https:// URLs, so for HTTPS servers MiniABS runs a small proxy on the phone (127.0.0.1 only, random unguessable URLs). It forwards seeks and gets a fresh sign-in token for each request, so long episodes don't break when the token expires. ## Using it @@ -86,7 +56,7 @@ Playback opens an Audiobookshelf session and syncs every 15 seconds and on pause ## Building -Needs a JDK, `aapt`, `dalvik-exchange` (dx), `zipalign`, `apksigner` (Ubuntu: `apt install aapt dalvik-exchange zipalign apksigner`) and an API 8 `android.jar`: +Needs a JDK, `aapt`, `dalvik-exchange` (dx), `zipalign`, `apksigner` (Ubuntu: `apt install aapt dalvik-exchange zipalign apksigner`), an API 8 `android.jar`, and the renamed Bouncy Castle sources from Minamp's `tools/setup_toolchain.sh` (`BC_SRC`, default `~/android-toolchain/bcsrc`): ANDROID_JAR=/path/to/android-8/android.jar ./build.sh @@ -94,6 +64,6 @@ Compiling against the API 8 jar guarantees no newer Android APIs are used. The A All sizes (text, rows, buttons, covers) are in `res/values/dimens.xml`; change them there and rebuild. -`tools/gen_roots.py` regenerates the bundled root certificates from a system CA store. +`src/com/miniabs/net/` is the bundled HTTP/TLS client shared with Minamp, FreshMini and GingerCal; `assets/roots/` holds Mozilla's roots (regenerate with Minamp's `tools/make_roots.py`). `test/run_tests.sh` runs the API, JSON, progress, HTTPS-trust and streaming-proxy code on the desktop JVM against a mock Audiobookshelf server (`test/mock_abs.py`), over plain HTTP and over TLS with good, expired, wrong-host, forged and self-signed certificates (`test/make_pki.sh`). diff --git a/assets/roots/032d371dbf31afaf7ded8c4817d515f8152ed5a6-0.der b/assets/roots/032d371dbf31afaf7ded8c4817d515f8152ed5a6-0.der new file mode 100644 index 0000000000000000000000000000000000000000..47bfc6ff7db9fe972841f2b269717a1d5e866d63 GIT binary patch literal 574 zcmXqLVzM%5Vmz>bnTe5!NkD}8>+0X;-Qo_>kDNCxy?Dfci;Y98&EuRc3p2BUpCPva zCmVAp3!5-gXt1G#ff$Ix#ls&Q?4#hEUsRA^lvt9PpJ%9QpbU~>=8*x)=q2apDunn1 zE4Vs4D+J}|mnb+pDi|3U85zil^8!f=BU3{&V`CGGC~;mBGXo1lOEA|wit6^283?ek zgMGuq$i}MO$ikq+oW#K5s%cvM>(CD6b0W1XHu^4zHhZU~6MQ&<({z(cnp)<8%~P+% zPI8dsJGNA~;LCfVK#kbvXZHTIX+3y;%7l~J=O#HtM5h0pqArlqQWfou{Hi&fg>o0zxy+9WI+n}Sj1ScOY?!G`9TJ<0HdxAIii>|7z|RG3_>3(g}uB!NhQKx zZi*2<50lxUziUd2{2u<6Y3Nvd=J)m2+W*aKKfJv@uQbcFEXef%lOe3|G!|s#m1r4=5fxJg_+sF!;ss6 zlZ`o)g-w{r#nn*MKp4c~66SHy4Jj%uE>UpLP4X~QGEe{sGYd-sg@ZzaLlj)Y6oT^e zOB9?P6$}-O42%p6-Vap#ca)iSrtpg182jra%})MVrbD1lZWYK44;GW7TeC zVNhaDVqp2meYee7LGu3e))z`%v(L|+v40Dz+R4+8mtE>VI8=*!A=M&g~+nG`$Y-udFXk_0%#*|B~|knpF9%!-u9P zI#qkFUEIXTYtY2VX}}K*3t3@C#{Vp=2FyUpKo%sx$0EieQZK+LW^T1a<4M4eCEwp< z8*TgdvfF?UB+U;}&jO4QHiO1vAOU%n#(f5jyAA9X*etMWv&bkZDX`MlPcF*QOVKR> z1-)KgY6*z159PnRUoT@0-$);akh1FwNFkhD#=XCOinCG zElMrR2hy4Oc{!QICCCYhIg`O4jmePVl+7`%(nE2jNtR{|2~ph#C(k@{^R}b4b^A#Jw{Pbqz0&~WJyC>QGDRoIYNk{)_2j;e zX=7??B9pllsnar^(rWN%S|(_=Xl_U&^VGw<&(nAAz280OJKwn%NYj!7Y0%yhC`BK8O^M@&Q9_J$sS2Xu%K+hHHUg{dLY1t1YgL|5`w^44RfhEBw9;QxGG8U*+4 zyTDo?Xc`0o0@5J*kTeJww1kkC6VCNNu~$!r2&*O?(UQ2a@%j3S>E(SVGJB4CMWEs$ z&U{)GPf!qD$Rc=f_mS)()1QTpj?8{Su7Xa@oC+g`FP!)M)&O2j=_v?v{KlxGra2^iSd%6%}`DPaYfu=@Hr!zszEVqfTk#@_~8 z`%1;>%FbNrFH4vgxbvHM4`7t=n$6+?<(=bW>LC0LCqG%IwQnIh&$!#IAekbHtVDQ- zk|1*dkb`#pzsD3Kw#mq9W4yCMCFwm$d69&A)>t#hkB4$`ecQS%XC2-+)+`q}v>e{E zS`U4$I2o!CB3&YcQx^A{KafB_) z?6GRfX0b2kdWu->6mp)HuT(X~>ye`+!BuBw(6LJMO2ZzU2{!wPKFuML*0B1xKu=Oe zp1A;lf` zEm^OMOGUUAwyn{0OYzQ=6CqvAFRAdJ!OKjvblEbKFyyHdDA`>1qIYVR?tM%t_-N;6 V_2(^Z#)_>wJtMjXW^vTE?H>b?c2xiX literal 0 HcmV?d00001 diff --git a/assets/roots/0f4efcf05175b60e4914344f85fcf18b3f826fd8-0.der b/assets/roots/0f4efcf05175b60e4914344f85fcf18b3f826fd8-0.der new file mode 100644 index 0000000000000000000000000000000000000000..c4347d3f5249399da3622cbe92e6efac8511c503 GIT binary patch literal 1095 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 literal 0 HcmV?d00001 diff --git a/assets/roots/1336799a38ebd57730aa49f89be555490ab47bf8-0.der b/assets/roots/1336799a38ebd57730aa49f89be555490ab47bf8-0.der new file mode 100644 index 0000000000000000000000000000000000000000..ae5ae3529cda00160f9576745bfb0168ae9309f5 GIT binary patch literal 592 zcmXqLV)8L)V!XJ3nTe5!iIbtRO6PsQa+Qk#7aNCGo5wj@7G`FHC_`=oPB!LH7B*of zuK+_e0~HX5OISA8)!E-yA*86ZxI`hivbZEQw^+eBUr$HDrzAzsP}x8cq>fow3aZX0 zSi#lVSs^GtzeK^=QNhT-$izTSoEJzM8XFpzni?6KM2Yj7SQ;1_nn1ZUwY$tffQ=pO zOD0A(R_#U>1|{Ys29`H>jvC$GTW%umabfGtZ?h-+w@uND?uujAcKdfm|J?CWgmu&OGt7mPVh^O%FJb(+J%IBX0Hcq%O8@`> literal 0 HcmV?d00001 diff --git a/assets/roots/14146f28d9038bfbf04a2093c205456baf4034b2-0.der b/assets/roots/14146f28d9038bfbf04a2093c205456baf4034b2-0.der new file mode 100644 index 0000000000000000000000000000000000000000..96e02ca8d4c522b0f0b31433a56bc34a52fd3a38 GIT binary patch literal 1551 zcmXqLV&gVwV*b2fY7N;f_C1)sj z<`tJ@mXwxc=I0eFIHwks=qWgsmSp4?WtLPLIvCi2Okox_C&iSY{QMGUM+GATLsJ7e zab6&4ZeVU`U}$J$7$weYVqjoqYyjmB9gj3IDIrG!BP#=Q6B9p!K@$@fQxhtr7y192%CENT zuga@cQGb5ywA!<$|I*o$Z@BW_wTeu&jHsG4v&eQ%OI(NIj;mjE{@g4G@~_yj*G9X0 z>Wr_OFJ_!q5UKNtiGSYx^s8y3r1^D0j_qmtE_ztcel=O3>JF2vBx9U&{_O(&#|iHY2KO!el31Ul5sZYrcYQ} z&-i-ogCE_UcU5QDPTE%Y{^rNPz*=3k6Pb~T57uPu&@vNbsS4O@)Ur?iPSMrtvSRK} z+|v~7zf3#2zK^kLDSuVUS6+$so9jBR{%SdBbs$qi_Y-eM;Ht+<%a1LYz_-x6+fdp4 zAScJ`yzdpWuQ&Z^QJCo|xlPDz<8Rkcv6YKc*UVw(f0cblUXQKlOkP6iGJ{J<=JNNR6Bf+$(*GN8!Yf~=&=)n*ii($|tZKl*Uvk!H43L7htCnr_{F^Q)=#m#i7j7cG*0|cJ9AxTLfG>7d@GhcXDd3n z`}p-2ukv3cEo3Xk;lTgV&FA{6YL0_kUvt&(G4-E1yjA%c(tajKGe6_YiY5<0ju>BKF(6oRXN6YN%?U3=-rLmI*FNEK18v%}G%R zNlnhk%g@PA&rB`WQSi)5)-yCRFaRlI7S=>43(C(gQE*NzD#=XCOinCGRd6gV$;dCt zEU8q`Rd6>lkQ3)MumnOw12BjZ=QTDrG%^MvQ%h6Js6puBCPpRXU}a=wU~XdMXE11D z-d`>a1oOIxgTU`Ca++d%i^=IX| zlXqLq{r#tsVMb!fd9@~oBcjv*#k4g3Mo8warek|YaTNWi( z7H4wc#k8=QU7J7DFFCvSj*(%_#Pbj01%G6(@{GASbIQd{6>DlMF}~T+m0oT5 zC|$2-DzloN^IR{(Z+u&Mc5i=VUaz!^VKHBI(C_Ts)<-T^2Kt?y@FksH@`R0B6)$7$ z(TL}{&sq({XQ> z^=Zx&S8i*E#_zQ?Mqg)~is-A=dN(1mIOV5VNu27!uW77$^_HS5wSEV5_qAqz0RZW3 Bb(jDE literal 0 HcmV?d00001 diff --git a/assets/roots/17a7d72a82ce8c8116af6a12f0701505259f816d-0.der b/assets/roots/17a7d72a82ce8c8116af6a12f0701505259f816d-0.der new file mode 100644 index 0000000000000000000000000000000000000000..f63cc7f924172a45ebf672c6576a7e9e4a9ac88a GIT binary patch literal 480 zcmXqLV!UI}#Mr!mnTe5!iI<7_Y28=*$$yt}DsEb4z{SR))#h=|mW7$gAiz+?K#7ex zl!aSZ+C3*fDKRHFGd)ki)!A7gC_leM!P!wkS0Tv6P}o2aq>4+J3!w@o$c!v#AScdi zXk=(;XlwumQR2MD76yifmIlU#CI;qFG_Wz!K$wjk>?0;dsMnYo*_oXfSauw%yxgCo z$~RxnQc{L_$+}rjE!jE6P9<;L+4iF={9yPwr(eYjzo$C=mpjO?DX*CC|8d2q5=&Oy zEVN&7W#_pb*~Lx<4hDQckIV8iGX7@)1{hVIpm(-l$K;_16K~UWV1_ zni@A4ze=)fligSLuPEk7>#0i(j`vwtEWX+@!Jqr{1N+&Mm&`luKUD|G^n82x-G+U7 z58tB5MxIR*Y%f2KbbHIca{0Z6?5I`j+ayo#3I9A@v5IG-c8T5VTg%gReU8kSXU18U zmi9-={*_0F%szv?4FUUWww`17#*=vSxeD`x$Bd19dvCOwbnM+^wxT+QwGr17=8IDr02uSag+vTknGX z9+|6KLpp`oW+{iI_IpvWJR=OSIFzh@#Ey&U{m&NbsOa3c=@b};el^% zySdl?u@zv|vi6#&`mX0nOM8RX9?qUsR>JWwOezBo3P}FEtRZ*hVxWQM=5-U&ugP;I zZ#=s4n3G|$_>}jVr}cmJh}0f&mgv0s!N}oR!y~u&r;iUZM6USxLSav*4xhUT_p9(j z!DfG!Us-Q>_8-&J(qB8(Ie&isYJIA9{z|3O9Vb^^bhkAtUlEy-t$zN!`oYI4Tid>` zt26T5P?>Sj^2LOR!_9m6b*@c+eU9g$!tq<%?GD@T*S}-3oRXN6YA9tO0TSdA7Ie>dNlZzp)Jx9K z)lu-wOV%?qG|&ghF$-(B=PQ6E6@v2fOB9?_i%K%nGLsWaQWYFaOEU6{GD|8IbQRo< z4CKUl4J?7s&;Sgg#CeU)4ULR}$kfu*GHQ^xvWZa%Irtb^8JL?G`56qF7`d357#SJ% z7bbCh{1s{PUHYWN;_nTf5igaRg_j*waAve_`S`5WbH4hkZ!OY(3%1-Ym~q^QaU-i8 zyWhJnzx?$h4V0d*mJG{&)WH7dvsh_^WnlHwpQhVN{8sih2|w9pzmmE6*P?x0qFMF# zxDRiW6k$E}>hKDdDb{W^M!cUH(v(j0X6Bmp2uf_fyh$cui^VUdo^zM)y0PEmvU?p{ zbE4+=2JPS_laew6Raq0ITpD|HRyt2pzQ4w*aTcHEQJIx5W97EZn`gjfdG2FJ!*B1{ zGe&P(lblsen8MTpk~+x)X@f+aLO(Id&r>-Or*e2W@MOoJGS!b&*_or%!wcCtl11pbr&l1 z`KPvT`Mzwjc#DP8E!OHEOr<;iF5P&2-M8!agii+x)OERCoc?RO zp0?oX2^-WKe!iF=_0oL)>L>SeHn)n;wY{n0UpdkAT0G0^hUV3?m~^)ae7P~m>;X85swAuG-$dtLsriJBjr{lsYTTZD}?Q6 ueeG2a{zo$Z>cn@fiTid%Y?Cu5kMFnT*;{6|q@Jqkc=X6+U(EMcGG75Um}y7= literal 0 HcmV?d00001 diff --git a/assets/roots/20dd24a4280ef8840064866941b494f3f30de194-0.der b/assets/roots/20dd24a4280ef8840064866941b494f3f30de194-0.der new file mode 100644 index 0000000000000000000000000000000000000000..75084805caf9a934724bfc907024692a58955f06 GIT binary patch literal 1519 zcmXqLVtsAU#C&-HGZP~d6GzxK&9f$ZD!v);vT+`d=cHdrI+Yh%Z9=0vLxr;MA<88Rzf(smu&CfL) zRpcKRiC$oo`W;uGyy^6O%g5)B`W>5;t9Gn<_vTybbGh1HHkz~k`%qZ;=CI7mrv zws2l+OXgd~I@NQRMt8^LO(rromW1YQm$`6nW~jCD7o`V3HPh7IC_Tui}_mnB2 zz%lO1x&TWh5w+8stcy!R-PtP}Ems%_yF6v-S~pEt)ro&+@R8Yn&OVX3XY*_IH8wB# z^kp*)WbW$Rz9TU;Jnh*3nC`HzXgX;3)+isaF<{8L>6!5W#v0#@5WkFefkbx|~+}wto41swV zm<$;i!Z!Dv=9=r-w|~iziM!LpSp#R3{(c&uQ#J3S$gkCxyX^i?)a+00zOiLx2qW*l z$!+>Sy>gGoeGUJ4DpW>jpF6+V(fe83Zmm<4J=2ue=k~oPl3~ng4Qw!)J?- z&40INoVazYKhS2=?}>M0p3k29a64~N|Dz>GR#yCcEwbjAGwY#LOAQI8!%ds!vhGWL zJ1su7#O61rsb2LQ;lI-SvBz^8t-|=vSU+6Pq_4Eb?Ub9%0vVrcE?eUCVnow8AF65o zI~J6rU9`VHY5TQ0ik|YoD@=2K!5@qg9Z72!pmykL9nfb#0CC;U7!4g$mdYm$5^C$jo>QeH)$QPh` z_cSw)_{nAl4nI@huI{%Vn5LxZxYVDXsy8?faItY{wRxPgWnpGE2ryJJ zP-0^aW#JZGeqAijbzXF! zJ=KIuzUuC48{>sX=lU+mcbsZbe^RdH^~HIfliH`W^H{ZdrOlrkIoWr5B3ql+VkZL! z13qA=$nrBX{%2tU1~;1lKZq|3;kJQGfj`#uh>s1x_GJG%e!}} z`NI52lP)}vaXlW%KME}v#q?GC%S;CWoE zA9wSd=rYX(oN9_$NlXTXm$vrDys~~+!P&Z^bdvRnn7Ur)s&z|Lp SnIFGfKh!$?Q~Pt;1akl<_O(j@ literal 0 HcmV?d00001 diff --git a/assets/roots/229a8437e3d9edafce3dc9904897b48fdeb436d3-0.der b/assets/roots/229a8437e3d9edafce3dc9904897b48fdeb436d3-0.der new file mode 100644 index 0000000000000000000000000000000000000000..6eb01cc84d343fbdcef5934f70abecd93db5593b GIT binary patch literal 580 zcmXqLVsbEOVtl%QnTe5!iH9-75;r&S-1?i*f_M>JkHs&Ff$nx8gd(OvN4CU zun9AHgc=GO@Pjz)JRDA?DTxKC#U+Mf1|lFKE*@Ur%;ci{;?!gXpOO?kLvaI9ka!{w zUzlTvN2somv8jcLiKV%roPi8Tf|*A&RX4aQGd(X~At*n;M8Vln!N|bS+(1s87f4zd z85tTGm>ZZyiSwEmA#n%7Ns$J^Z0z7LVPb>^4l^S=vl9c$v^%z4H#St~A5-7wGGXK9 zFwyEhXO&v1mdJO>8Jmv9wOPW91$HC4A3 zCmSS!!&Fw7k?}tZs{u2RGT;LSkSsq)02l|1YzDF*J|Bx1i%7emD96QB2P=3su0MC| z5#jhqx-Z<(KBAB$j06=NT#*$b+Pqc_biG!Kp=M zsYMDw`S~RZLBWm+?uG_(;=D%22ByY_h9<`5hGtRXye1X~rbZ@Uu4xo?Eo)*@LiP+J zD+6;A6F-AN6B8Fx6B8rDs&!vhH=Y0e)SUbI)oE)dyzth%6Pob&%%07AbSLe6vbgGF zo!`CH>cQH)n^=FAVtay*wd{=e2^a^cO!F@{V5>p1x_t zZI#UCu*;dJn}l}HmHza@wAic=AC7|0S|k-y`OqW=jk;F*gu&sooX+*z_U4PrWmhbHikzt@3tLmLBgYRor-wQ$@??(VjOx>F$R#J9cvI z`gySAT#tlW>aFkj$9k$WVz}QWABgk*=yztmKL5iPv#KV4d~o9Se)VUmn$>0xj75t- z&CPD%$j~-p*H%AMaU-jp&%NZ!ss+NaDRp0Ni>&)?^+f+*>f@bN4mls#wpVM+zWy-Y zqIk-hn+20~@3gFUlUY=yW8bLe^!&<=qd6^Ug5BlWAu@}%-Vpe(PV;*@M~8UKj_H-> zUukQ-i!gqg`%-gNDkE=`@EcX`SRIEVQO(N}biPYDU(NcE$;8aaz_>WsAklyym~Lf- z85#exuo^G}DR4rTRc4Ve5Ni@4H*X+a--Spat?J{%s{8_CrEiN%;hmx^`hgiU?Q~EELdfutGSt7Z| zZ0)2+JHL2eea*4lue~}{Vea?2Tebe| zOp-jEW4-=zu0_u3>Sc1he_qb}mo2*UcGIlWGwv5JoUmZQ(FZRWS1cBpbK>BvHKGB= zy>Dx@x{IeeIII0>T%r|kIbpl{>A>2c+q})`Oqq?Oj8?OC)V3YZZ zWvdMLBR3acXY1*ng@SukwjEkq6vFl|TjFQ=ZB3n^dAa4)Nq_l$`(_v!7!(R!Q#E}& s|406ut*^7B*S-?CJWZ^KO;Poj&z}B*-8@g?l$v!f?fcgnz2(VX0P=HU&;S4c literal 0 HcmV?d00001 diff --git a/assets/roots/262175b10d1adadd3525515deb67dda91edf2154-0.der b/assets/roots/262175b10d1adadd3525515deb67dda91edf2154-0.der new file mode 100644 index 0000000000000000000000000000000000000000..5a3dc5093cf825f6172ccf9b5862018079b598ec GIT binary patch literal 887 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 S13O1X?xmW{y!iW1vr+;2{@4uw literal 0 HcmV?d00001 diff --git a/assets/roots/3053402c7b5dbe592758be81f8b1a0c89bd9db40-0.der b/assets/roots/3053402c7b5dbe592758be81f8b1a0c89bd9db40-0.der new file mode 100644 index 0000000000000000000000000000000000000000..45ac9386a87a42ecad26979a2aa0125bdd837b04 GIT binary patch literal 601 zcmXqLVhS~AV!X3}nTe5!NyJ|{Wk=ui{Jzi;Y98&EuRc3p2Ap zlp(hPCmVAp3!5;Lv!9`=fij50#Um3^R9al(Se%)t5R#glk(Zy7pPrdotfSzWm#k-~ zVxR<4#>^v)Q0AVKpOl!R5R{)^qTuYP;BI0dC(dhRXkcn&U;w0yjH1MOP0S2T4K1Nu zn%Z4vAi%~B_9YV|8>@CB3xg7K5(CS}&1Ws1s5(ErC&{_Hv2u}Uyl67xy+f<-ZeAvx z?%mM5e&*>l;_ z)ac0e+VkfI&AY~L0+!ZSYVJ^&u{hZv(SRQqHnPHujQ?3!4VZzHfjme+nMJ}ttU+Yy zZm;R*Hfz@}F}yduIkI_Ke&OkR2C^Upd@N!t*roYE()=I;S%A^Xh8%&+=?n%bOa^hE zUbCI?G@S3ikh{Ik?1QhM|L4=4Ryq|E0~SXgI900lY5#;+?xvmujrqq4W@SA2$Yc-` z!53C`KH~6ofu3D`>mKh(DRn;@I^pr(U7@epj|8taU-?dPlVWs2zpc^7tIAWZMF0T# CiO)X( literal 0 HcmV?d00001 diff --git a/assets/roots/3091c2d60f82ea189edb13b0755d9b046e67f050-0.der b/assets/roots/3091c2d60f82ea189edb13b0755d9b046e67f050-0.der new file mode 100644 index 0000000000000000000000000000000000000000..e193a059ee8b42a0b5ae782f31d5fec71e18908c GIT binary patch literal 1371 zcmXqLVhuNFVzytv%*4pV#LL9|baKAo24T;R>_y@Rylk9WZ60mkc^MgbSQ!l54Y>_C z*_cCF*o2uvgAJ7o6hIs^_~4w?6yx`?qIX(j5dts9?CHP&lRfo3d zG|o*As(SQFug!VAJS?Sunt#SR+v$f|`VZ`JIKUmV_gnjB)0{OD0rf2pe9lg~tnku! zPteTuMq4#Et7>iW_*wMuRLb{fO#;0y<{s+m_+^pS^(Xl!~ z4Gz`MomVfHn*JhtGLG*m`4gpX>Ru18*I){|57KSbo)-cVpKg=6i-~ z&y>H)ueQ5(f1;AmvwgpA82u4Q76~l67kf=?@?OmhqKvuyiM{nBcABIUY6+59)ZM&W71soX)!QwD(&}?>R^8FNF%{scg7b z`0LBTU1#1(fBo0!c!Obvt<{NjRWr;bSf?^EGcqtPb~11<-~%R8S$;;w|12!PoX}># z58?}h_^bxZK*~TCB*4ca#v<}$y|!ARl(vU@tA0?L&ifCY!i9FosSKD^fT@g;Vg4#7 zwcFgY_x+cfHc8KJQL0zgj9p5nYY$r8XyRf!_HF6C1Agskl?#6^i}Ze>H}RBymNqRAM!>Yr6LCcDTtNaO-vld6hJ*>-V_`NzKtK3lvg z`XE?3_q0h+@3XRO@niSfR;%}UYL})p3MejnW%p&B>i-(id*^yo?N22YaDCV3Ok+P` zFZ1WaZC8W)NgW5b^3Ody-|Jpvr2El!C9`Ku%inVCe8j$>x4O#9rts1u=7sV`?02P~ zIGy8Ra7}F3%B>KoS+VzL=;`F6Ha=gr{lA?RZ!iwNGE|Hh;l? zr{z}lF{fiU6v%3ue7#b}$@99xCzAEEvD4v1Q32_4-*ZYmS6V;0)@~1e_1+WY;JGvJGqdz zfxlX;Tx9?M+niH2P2QsrA^P%mq4Vc^0*4vytk9ae?a(#*3E5@0bN==0`BY%ab7!Wc V>7DB*+>)=F6|M{ay}c>-4gj1STZjMv literal 0 HcmV?d00001 diff --git a/assets/roots/31e4c31bde81d32ad0d8ecacd40c3da4e71aeb38-0.der b/assets/roots/31e4c31bde81d32ad0d8ecacd40c3da4e71aeb38-0.der new file mode 100644 index 0000000000000000000000000000000000000000..f81b63aa1e1b222d41e2b079e2088f3680dbd977 GIT binary patch literal 967 zcmXqLVm@rp#I$+=GZP~d6CHzYMDH8?d{!96EGDKRG~KOd+nC$YF#!Pr1foY%m@ z(7?dZ(7?#j)GSJz*Vq`uH8M7}G_{Nx9FA;aR6-6sMpg#qCPsb+gC<5UrY1&4hP|be zKO`yTtE_uaRko&f?@g7fJGTEh`@!kK>xz!BxY@HpWPN$MzFj_F@Fi&Ljma#J-P#Hr zy|h1Q>4Y+0^Emv#(&Fu>7#n++J(Fz{SNomi-SGf$MW)m)WRxd35 zEtOro@yEH9oI0O^uO1e0+0nN2Ter65t;NP6H-8mLeOD5d>6n;ZpZ4@{R>KCB&-dG0 zJN`=6m$u8xZH4B5b`MwX97j74NC z^Y=3A#HK8vRT9CknH8t(|99>bawr3nC@_>68NzJePr17HR`I5Yed{KyouNMcALGUi zK@2;5pGYf~NY@>B7niA}x<2Nb?5#_cCqGZue9+c@k0p}-W8{^TR_~eXRaxXtx~-gk zuW6HX!TdSj))yMyt!7=Sb9v_}_iv{@eS5xkr-%I>vF!(Qzr5mH|K{`7=dAOV$#n$> zUYp!_reyNBowt^$vX)dv)j8NTd8-K;2kY?x)Y3XEdC2Q{u7GK2A9)yQoc1vUx(PYMuRdbS|yFzQ}f3+nG;lCc0UZ=FXD3 sVDKo`k7YxAw?bX{LEEWK7u{xA+f?kxUwDBxWl@Ekrr+elOF~4V0eot3SpWb4 literal 0 HcmV?d00001 diff --git a/assets/roots/322ad42b3fe752585a7c1435fd71b9763f2c7bd9-0.der b/assets/roots/322ad42b3fe752585a7c1435fd71b9763f2c7bd9-0.der new file mode 100644 index 0000000000000000000000000000000000000000..a8b2d8e5d6f8b3e1b60c743bcdf2645ebac9c624 GIT binary patch literal 723 zcmXqLVmfco#ALXDnTe5!iI?F;zC`sk%j_9mo-?=%xY#(f+C0wLvM{q4lp1myaI!In zvaks=d4w7Y8t{WS>^vM!r74L8sl_FRVg@20Aub+X-^}Eq{NmJP1)q`>JwtH=QIL2d z4_}yLh)1Zdk+G?TiHW7Tp^||DNP?M1GF3OYDl;{LQAy;=_k?n%Ib0SA3wzS-HYdOQHE~0?_cP1;Oot2@gBbST?|B=!)+lu~ zXJo)u$wL`33=df2Zw08y9A3ksbkxfC{mbwOv&9D`g9W%Qp0$7PZ7Sv5?s6$0Eie64}C(6y;X&HTF(Fmw<-yovYV=yBf%Yq?K7D48$6+D?myd%#Ga) z2951ZPK>&@)_v8h*p{|q{VE{N&*;fp*~xu>_ry%S$D`!-A2 zJ59|O>wm9Z6ZGY}_O45V&l+i^EhYA!pv+CZ^&)H z$;KSY!Y0h*?qn!oAO_-a3G)Z1CYNNU=PUSR=4O_prWl$Tn1E!Mg>_*v0i{VfnaK*l zsYPX}MGB6kB^jxCC7H>IC7Jno3PJh#B?_)4W(IQNyherw#zsa4U=StF3luaqGBh$a zwKTPiqP+vk3MT|w{qS7s&$dBIfY8I>J zUVHbN_wetPi3WTiX?{k=|12!PsA@Cd2l0hLd{zTyAcY)L%;^jUDNF|H@0LWZP~82} zefpHnr6mfvmHsF8PL7L^_^m6&kacp0?S&=&F78tp!xsH8R^@TJzlzB~_SHkZ4!wsz um&-C4`F)z76I$|k-MZgPrQW^0^yd(>;A_{I1OeNa`kBnpxgj2mUBv*44Z{Zj literal 0 HcmV?d00001 diff --git a/assets/roots/3582a277b57e01b32d31d926b4f7b4366493d235-0.der b/assets/roots/3582a277b57e01b32d31d926b4f7b4366493d235-0.der new file mode 100644 index 0000000000000000000000000000000000000000..62d354adf604f533673bdf931722df75b41ae872 GIT binary patch literal 531 zcmXqLV&XSwVw}2wnTe5!NhB|8PvolLeKRc67hTF;V*M;v=GrX-E;bIWHji_*EX>RX z?uOh3oNUaYENsF|&VGh622vmnm#}bVNKt9Ab4F%fqJne2o{mpRik_jAfdojFSy-?P zB&*=+?5q%!pI@Tj>}Vh-&TD94U~X(^U~Xh)ViG0JYhsMVrJhA)1_EsCVD~dIvaxD6 zvM?wyCo!<326(OOshsAuN`~X`;zeiwMTR++|5cmfv|IKp$L-=x(WRfy&+%*evj0b- zT5Ha=*65;wOMh=CZMh%Zy?3KaM3NKd@n7}P0?+?B{5^2vu(r|jf@Ry6DTK;aH^h4P zr~7;qSnOorU?2mdZ%d`7#Z0h;GY4yXkD(kCj7T%UWZ@>?d7G`Ap&%$cJ z45SSBKmz z+4{4C?$Y_ cv`9$dPU4&x!SK`fw@Jn=&b5kc5>w6r028>g(f|Me literal 0 HcmV?d00001 diff --git a/assets/roots/36341f8404085341117cfe8fc657667384b14135-0.der b/assets/roots/36341f8404085341117cfe8fc657667384b14135-0.der new file mode 100644 index 0000000000000000000000000000000000000000..e5e2343aa815011b5044128b849639f2971c30be GIT binary patch literal 1389 zcmXqLV$C#YVh&uu%*4pV#K|yeN5}0AT?dm5c-c6$+C196^D;7WvoaV28FCwNvN4CU zun98-dm9QH2!c55!dy;8i6xoEIf-S7FhMS1E|<*W%yflBy<$B>Nds|^N@igJXGaBy zcu;4*IHwBb49)#I$S)@^iW}FUE}Z7ZUGMt)_7JtTU{ApbL)SN%={bEnJVnE z9a&yF?%l_%V#qPE#vtMS+I(A0de%HQD-6l+WGeRZ*^yIIKzvXkzmsxS~ zjY}qz!=|0~;b9Q7iI+Ni;&{%owU-T-?|Jd`VcNAzznQl|7Q1+! z&Yt(WH|_4s^Vd2Zzp+f}($vh&wvu^XTFX?(_xZr{y{9Gb&P~{KC@o>zMwWLcpSA6r zmFBSGx!~JISLW_->5(rEvI*|Jd%l6mKh>ck#y#M~>}T?g)lXbjM7Pd*bT8`a@t;;M zY2HtaLTaAm%-PImE52xxiO{vWO)ETiU#!w_*tbYb{{9nldBNh}6Sz{H7xC8oU}Fq5 zuHC@9}Ql}`Gee`Y>kcbvbt*>jThWlg@B($l^t?yx?hy{#f=i*o*lzn3?~mCDbY zx?;_@TMv&KR{y@}w)xVY^esstyEwlmZ^tN(mW2zxIO(|ttB=}vQW z3g79id#?E(tg*@K70&%#{o3Hd9pCjwuWnoF6J2=nh2g#z7Hl31r5?SybWg}KLh645 zUsLI`?I+oD_}>4w_&B5R$o6_Z(F>}XdzI#Dzh(XA=Dz0bfe%-#AH8`uEqa2mpIn&L z>kaRx?VhGAxa*U1TS>~)6B?!WWleWoGH3iU;$`|=v?t{-?UJ%hg>#$;31nFFm7uk)%le~((ozJI0Dq|XUT z)1QbeWS{L{?6BzdL_zL`E-}+o8Pbt(e zb^6}R;+N!-KHq+o6VPMd!ZYps_S4J{PUS8X@saMl`<7utP|f;@IU6m6FTNDwIKZ$$ zMqBV!d4bvNb>}ME?=%%W<18r%&0N1YWB&fT+bTDInp>b5`epV1sfnLYI^@e<+&6jm zz~H^#qVy;T+e*DKVkc)Neegb?d+5{ID2@RUXt-TF{W~t zsOODbHp^Y~DtFkk?1@>>{!n^-VBgs{I=_DW%l%+gv#R?^`c%VSCT2zk#>Gws4hHsJIWzRrj@t?QSJv$f+dAEB z`8DKF1|~CLC^IsgS{mB(_=IC#ruyjB&78JqQl_3o&g@#2E1gR> z8k%>VR6Mgf=WMsktZVx{E*YgA>^ksS-|GE=$5$q{T?}~4e7m?j-^()i?LD>QTWi;A zJ6$YIdhDgmbUeWOLFS`rIeac@0xtb)SzrICT(eC2g}KSx;FF1~76q;MDiCWsWOZVr zaDmy4^Y6ZJnOu$b`O7|M(XN|}MKPa!58mb5D8qXsdEeSEBELJ|EOO-YIddZ1;X)qs zs literal 0 HcmV?d00001 diff --git a/assets/roots/39d28b71fe1d19b65fb3f1288f23bc04595c4395-0.der b/assets/roots/39d28b71fe1d19b65fb3f1288f23bc04595c4395-0.der new file mode 100644 index 0000000000000000000000000000000000000000..1e927a7afe06c270670d6bc25c4d465db2e0a7e8 GIT binary patch literal 914 zcmXqLV(v3&Vk%p}%*4pVB*1L-@!4|0l?!%jq->Ps{Pom;myJ`a&7m8Ce;an;7{S44N3Zn3@aHzbQTHIERDpF(!1g)GQaJ{9Zplk- zye@a|@AU_#r!O{6V{hxNd-12ggQTEo);V)vP&-Tc!p2{-S^Rub$w8x&gcgr9D_VnJeCX{_;|J2P} zlz02&x_&wBDQX2hucLu6-|8C^Skma^IW)V_ne~}jhi)FnV1keK@Hao#ijU%z$=y=4>cw?qzQ zVDbWnG9yDmy2g}zK9A(rhIe13OI+=&wK>}DRsV9|ZEf-U8{&)0wTy%<8`GUyls~zq zo4)DT|D%6HB9Fdi`2qHg8zibR_3{p5LfoBYYm zyzW}IGxc}J?B04s;o;2qU^mg*8M8lpHn?hM_^7vK-s7%Yx2D;d3NMZS@UtMGz}-N8^%tYFG2u-sL@JUi+4LlJwK^__B94 Uy+>lhq8Hsv%iC;f{?uq20B`eLrT_o{ literal 0 HcmV?d00001 diff --git a/assets/roots/3f1855d3c9a93b24f8b4f95a07ae048c20cea42d-0.der b/assets/roots/3f1855d3c9a93b24f8b4f95a07ae048c20cea42d-0.der new file mode 100644 index 0000000000000000000000000000000000000000..2f6e59f424ac5d56f689780d408b70d37135710d GIT binary patch literal 582 zcmXqLVsbKQVm!HknTe5!Nx;l%TF=RtO%Z1=FvcEz8qQ$A#m1r4=5fxJg_+qP*^t|S zlZ`o)g-w{r#nn*VKo!K{;*oPnEiEZd&PY`VNzF;k&d*f{PE9T?$}Fi=aL-NhFw{2C z1Sw?ZQNoZ9@d;LNb#_(=%Fi!RFfuSQFpv}H1(Fsbtt$ZYoD5bC_ljHsky>7h5sGA{JEaHZ?i3F zpD=047tRak>rXUrADq5*w(fng=f$0Eie^0??s-qe}y8=q}X^tpI? z_2XS<@69*h2T2PvGX7^_HDCr(27Dj^evkkQFb>&}V~jbS!61dnpfsp^`w{{FbzTD9 zn_8ls9zN4DW4g5|E)-)z9+vY+?kwmZtWIF_k>61m!FnnGB9`SNxScueXW_n s@vnZ=_kb19KA-KZ8LN z6Bknx6C=aIb*h^fCGLcG*t@;{x&F~><-$j^E+(ca#;;nh9{4m*TI`B$ZNP@$^1T&S z36D~kX4kaMz9!YT_P@Mn{ne>*LZ_dJw6Gk!Z?;)%e%IrZw`Z7~yJ0M@;r3_w$q$#d zT=nHF^8ER!{qf3MwF_&rH(9B!%nfo`^J8D$?>oziCr(IQ#w?=n>3RF&jEn)AMhC83rT-&ocbpMgx(TsPM6&FS@^c}vCCHz+Sv5~;EZPmkJCes&(2#TxgvC#E87KStJ|+r zW3>bYKUn*p%Kc(;r0~gqhNy2b>y#5(i$6XQ*r>Yh>ZIMm2YDV?-r8arzICpeb2ZWH@t!#jcLSb-knS{u>pOw5c7jEkKN91Qq@DOOgPk?}tZs{u2R zGT;LV@Phey9JPd()N{$kVR`FGO|>vl1&I-ccd zJ>d#R%%+5_2_H_JV@~?n{q}dB)*Uuq8%_RBv91Uo5B?V|iLY#mmX$woTGz4IIij*| z-KnL)yen-B_9{pwF3GX@<81Wsp!@$HZ$1ht8BLpW=v#E=v}y$&XUAF5-y$VD?uB-k z2Xa@$aLFxp`g?qC2mgUj6-+82lh=2)cbxQUkbQL_&n{8VY_}<&`n11kCf12X#UI6D z+}T1r8LA?LHgLUf{kw68acS0M8KGwharv_KC*_q-w3=If!j*)Sx4ih2Y)hR;xt$s@qw4|YSFQcHJR0iznZz8Nz9A$ znQ}C4-3z|B?)~4-K6ty3Pulohh{Qkseedg9vYj6n%(xv}x`{WEy;W4%^4iDyE7ka~ zc^u!oOzEq!dDK+*`PI85MDzb&6G>_Iyu_8Rc$?oeIYC3|@y4mmXIq!u)#rbn9+bCR zzqRJ8SMl?sb_ez<3bES%+0N|A6n#SV*`pAPua)gRvca4DTC3SCj_dsRv7x9}^=Wy~ z7iJlcWdZRW3I=(fnw(eLPH%Z0{9Ey$KfltfwjGgL-`s5_J9J;PKAiWal_P6oj>$>c UHy+y76>rmy^i^gy->dHd0ROW{V*mgE literal 0 HcmV?d00001 diff --git a/assets/roots/41fc0249061723abd565d5bce6f9562e79c7c8e8-0.der b/assets/roots/41fc0249061723abd565d5bce6f9562e79c7c8e8-0.der new file mode 100644 index 0000000000000000000000000000000000000000..616047950db54c16921f77495aeae1e84a603d16 GIT binary patch literal 953 zcmXqLV%}=d#58vSGZP~dlR()t4O3YCR%Fi!RaCcI0b~KO^=QT7jG%_$W zG&L|VHi{DGHMWFs4GoM9qv-9NCPpRX0AXZhU~XdMXE11DDeWIH=x#IN$Nw2)!`q{=Q5tG+u?(dg=-{f(e ziJ6gsadDu5zX3Nef@S$xSb(Xj&43@o5oTok&%$cJ45SQXK>~a%Vk{!2{3pzk;~p)3 z^V2#_^ju)jY17@N#R89^p6AO|`y6#@gDk-@>|cl)N9jT7qT9^J4( z?QBQ2woz~14#mQV>9y<3Mb_Ir|32$c?YF`iGg($SmsBi%?3rj++~Ygt!Oy}Y+r;>S zE99>&YS(A7Y}B+_FFHff;Gf0>hMtQLPydQL@%M*-oAsg!r-|YvpHXG1d264eR~j)Rjv=Y@E(gAnL|E3R6qEa^rTVVZO`< zr9+pKWK7TcWQvAJO_*`-wOstE^5dubY@IXb%!;_6VtrrQVOpK(+Q#F4g}XY00Rez# AKL7v# literal 0 HcmV?d00001 diff --git a/assets/roots/4264562253e3c442d08ec17f32fb2d58f406ace3-0.der b/assets/roots/4264562253e3c442d08ec17f32fb2d58f406ace3-0.der new file mode 100644 index 0000000000000000000000000000000000000000..71f9f88dd2a915982047b7b447940d2665519a9b GIT binary patch literal 1525 zcmbu8c{tPw7{~qQde$+9WJHc3jcU#8?}tIdBZD#HOraRt;h5IcxCUVyYbIwsid3%guz8R~azvZNj@{?kr)U4&=l$b-KHukgzt8)}_Y3loD?=|H-WE@-G@$WTD*{*59q z&>=DYhch3h@%@_sQVhSmwoQ!G5YvK<3!CjN7yO zb0uNG^~CnxRp)@uA%hTkM z&$Zsoh3nHi-!@CXGf8jkx}2O7m2@GA>eCHHSF_p|jr2M*DXgeqfibSrz3~JsAD5C| zkYclD9}!D+${9rBDOGiLD!R=b@a}^VZT{m%pTr4HTJ-XtraH<^y8F^>9gi9uSbPP1 zc_VINXXn*Ag=WNSG*#>`+Q!$(ua$1aD9$eyB-C24Vy>(u4hEs_Jd+$2OlrkC;CmGw z^>E`)X)6J$>}I(NnesZ#b;HzxVH@GfiL+x z)!J}eGVaj!%ZB&U!jg6xYtNhUf z1WXx8VJ+7NYzFRP+cIY=Y_FOqq`t29nB~t#BZ}csNiD)(3o$p2OT9vW9T}YEL@9TN z?6~D%>MN0!vnyrTMOv`}3=W6^MZ67=w+8a6M9EZOZMA`7YHs0?ypZ5=Q|cU0nkmeUNa{j>+*Po?-?0aaisIntNanlf}h3K_H2Q zv((qYIT}LdAZ6D`T#8J&?B$O0VzO~|OfOatlf!ZMW%}W$qFWD^7t52S7NHnYn^ACKyK}mF2P3THPFxPyN>qI-*~Qgsr=s1Mrvebz}m4I>Co7l znZ;V_G&Ac6I(eNMQ6rUnBD6eHZnEoJK_Zd#u&8i0r!MYGZszbG%kQxDuVv zNDOR!;u1e=wz_b3Nfp${?lTzdP-pWNn|<(am1oRwxwNYCE3W?9q~ZJedbQ7u{?sY^ zI31e!HHoS%{QXgssj~Z=Nnx6{&Fb^btcQnp)>gyss1ftiPyaX>3;)70=*tbgh^*~| zLK7FcrLj{D;l`8As$ntxdH&L_0unLh2sAcBRIc zuNd|=r#~292-KWQ{ne@XVYJDuB4b;Q;B-g+>t}{q{X?kSLi*TzUO(SoKRw6jkKfd% zUF)TUsRqm6Tp2al-7K~_9w2$1cTR+pgGU>!$DDSSCZk6QgN8$A36fcnChYMoe8SVe E04K$QhyVZp literal 0 HcmV?d00001 diff --git a/assets/roots/42aaaa5ea487b4f18a25daaf86f22533b7ee39ab-0.der b/assets/roots/42aaaa5ea487b4f18a25daaf86f22533b7ee39ab-0.der new file mode 100644 index 0000000000000000000000000000000000000000..db0e85a5da32a3aef203bc6279821cd6441d21f0 GIT binary patch literal 1038 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⪙?DO#BN zPBrT3Go$C9?yvUz^?5?3@@q=W?IUYry9JM*yF;hsG zeQA?8+s)2)&(^j37I-D#N~Ff#=1*rds)O5dZ>V(?Tz@t3tliF22PWi29g8bTy&m&# G@go3thmqj` literal 0 HcmV?d00001 diff --git a/assets/roots/4b1ec7d2704f8539cb63fb2d863f09b26393e823-0.der b/assets/roots/4b1ec7d2704f8539cb63fb2d863f09b26393e823-0.der new file mode 100644 index 0000000000000000000000000000000000000000..70a9ec9829df763ea8ee09dfa1b31e8ec996233c GIT binary patch literal 566 zcmXqLVlpylV%)xfnTe5!iJ!6il2S;rFH3jMQ+N4GTMf9_IJDY4&e^gsGaGmravN~6 zF^96S2{Q$|8cG_7gE(9~0wJk6nTZO{`MCv&d6f!|PKF8wav&*Y9&w12tFuCgPq0Ez zetwC9v!gmBBwq*X4+%hzR>(3EAN5KQ%)v_ zPHH!2rY=r4NHmZKhJ&m!i-dt#gUFSX7tS44#rhYd3Y*Nh<67?>zvpZq3sS(xBF2JU znhzw+&&c?ng#{QvYzF)wzA%W-YQPMnkRymWlffX3$&g_>qkGW*Yq!-8n5wq5)qlIT z{kOI6T8-^fRp<5d^(sfWrf^Kv(+$mx-D@Dqyd%yc0;pxZ)_%M6l=L4DvY$GBp2pyp lH*M~f-5X3zVoGj^s0)c@URUXU%BbV9^$)9{W?Wi%G62LOusQ$$ literal 0 HcmV?d00001 diff --git a/assets/roots/4e9b3837a2d6eb46985ae7cc638568fdaced325b-0.der b/assets/roots/4e9b3837a2d6eb46985ae7cc638568fdaced325b-0.der new file mode 100644 index 0000000000000000000000000000000000000000..3bbe4a913acecfaa269480665671716094f7f5a2 GIT binary patch literal 1414 zcmXqLVr?>LV$NE?%*4pV#Lw7$DPXlzsE&Ko-i3FhjvDZ?acZ@Bw0-AgWaMFGFz`0y zHsEAq4rO5zW(sySlr#_rakzK{LQ-=w6BV5Ea|;slDis`^3>6LJK~l^-5)i4NU`K@z zpJ0Wc{QMFHXGeuHV*@#HUL#{eLqk&oOA~VwqbPA+6AJ@VkdOfoL{ZnWCMG3h&oHtw zFgG#rGZ-{6aWOS9F*0n7x7En4ZBhT>9hg7)z;}OzBW{v&_RF)rSvmV*g7EQ*#nlmW z?=1Z2;w!9`&da$$;Q6-qGJz7=9f7xEw}hESmH1>?=5L#Nzt(W0lJ$#=A#eV%?(se{ z^O-cmD&+_9J1riEiZ>rSlkD{40o#G48SmfBp0WE9kwy1CHP3_^Oi!?JikKE{GAZPBUibBY0mHRd zv)28NX!L#|m7d0$Wp`O#dJXRfsmq$zR_@ab-Plt3MKD3>fGEqEY$^F^KiTzXOq(Ax zL1@p>6gVvThi61Wx zDSzAXXHvkjIdN5KF+g!I2_@wTJkJHsJ=2*LwB>O^o$*nhzx{7r1vec3FO{=xZ$gaB zl1;N${t^k!m@duyRC>v*wUIOLGJKC!%dSe8Vpr<>lJ7keGb01z;$(wFaJrRMW|1%u zYY^FReC4IPn>50TS*G-e#CYVJ2TfXGAPY>`vV1IJEZC*_K+^n-jQ?3!fO(3|fFHyc z2Ju-9n1K{>G6d!+U@~N6h)p)kF-f>MdE3Q59D-WwcfPK>vE%8!!>boAYuOT3)DtwP zotsnd^9q-JE7oT@zpcwmd$_S+`};Nd|M}vMHmNt$ zi82qQ-5Uc>zsbC~b={8WnX_i>3BAQQdH3R$yOwi4wg^;8JP?a4C|7)S%j6&D>zYrC zZ~l>5W4>X6+vE0S&!i-J#P8d`eA&5Y&9rJ&H$Cpxf^RrG{@X75XB9QuK%r{?YhjM< zElMv1!>t6rF0ptvk?ZZ;0Jf4fIZP9_oioz`2Jp<%vdO+>#$Tsr%_{BPm0B%TfYi5 zSFF8eI5B?W;*)*~9f6aMorcPz@fBP2-077lf5Y{UuU-ot=ebkO(QjU!dt^NTu|QsS literal 0 HcmV?d00001 diff --git a/assets/roots/4f59a39453cfb9559e7f6bd8c54da53da642b714-0.der b/assets/roots/4f59a39453cfb9559e7f6bd8c54da53da642b714-0.der new file mode 100644 index 0000000000000000000000000000000000000000..86b7dcd0b416fcbfd1636f5c019403ede4ddb413 GIT binary patch literal 837 zcmXqLVs@5qwPB{BO^B} zgMpDG!O>~FbfMn1Qdeu^Gg(*9Tf}> z*0AF)}i2s%R)Y zRdMxk;d=LmfRft08`nF^zRb+{9-)}ca~YmCeB;(ugPrbQ?IZmt`C3y-6Hm@&Zwbc1Q{rc}h`cPmw8PecTBE+ObT9 literal 0 HcmV?d00001 diff --git a/assets/roots/538933b9a76ad7c55cbf4dc2d4769ce2bb50d20b-0.der b/assets/roots/538933b9a76ad7c55cbf4dc2d4769ce2bb50d20b-0.der new file mode 100644 index 0000000000000000000000000000000000000000..603c65ed6e7bcfb421405f44c86666b82224418e GIT binary patch literal 1422 zcmXqLV(l_$VlG<1%*4pVBv9J>w|iX;JHxM)s>>f9Un^q3%f_kI=F#?@mywZ&mBAq1 zklTQhjX9KsO_<5u$xy;T48-9Q<_}IyF3C*KSMbTq%`8bxF*GwU0m(26>%wFLN|SOj zlNEwfi^@`q6dX%SGE(zOGLsWaGV}8kg7WiA6oO354CKUljSLNpjf@PyAWEDUC}?bC zXk=_^X=)iodj~WzDIt5Bk(GhDiHV=Vpoxi#sfme^Ve-DKWUE$#1oaSP|8jDg(OA1m{V#72ue_uhB+%ZXde<`=J7 zE>sLOXx@-}-N0{J^u!MZDy#RqHl{f9?p3tXw|O~__q4)RC-pP8@@KzGF_>U^c5-T? zqTyEM0Aq8d#};pY7#f^${g?FfVysc6Q~Uj_GhvbrzUNl${<*cxbIM2c9}|~0KV{pT z%-3+|flz3}GV8Q0mhHMHK3upc^709yEGVWn>A6@<3nAmg1w_$=w^!Do_ zrFU+yc+5X=P@`kZOPN#4H+~G;UhU}<`BXu4MGKSWFN-Oevpm`Q&&pSdhzs#FSKX7} zIQ`wv=@lAFPPj~ZS*ymnETU+3rSJ{G_;p7ul1wr-wHfsNn2Lf2svLe(sNt#Shf@Ccy{~0vhiGh3wE0uR_5Xc!cIp3a z*#(lUtAG8_ZghHKv1|A5Rex*w(feBoek420{B&;|hW$MpKQX7P{SduCiI*4{CdK&P7r1=>c|Ff_F z^H!SyKZq|3;|TF&3`o0OjFp!J!@{Lg3XQDADNAI-Evr1 z{Hl53c9&&*GZ|+;*;BtycuL)=WpZT=X0|PC-NtIExu@#p{gdyBu&9=5-pId4TyXa@ zwlm4U8b3Aq+~O9<8$N3wDdfmrUUPGh4V?g*PHcS=*EKVm#j+{zRdWs$H!hs&hksSCZ+Bb%S}lq{EO~2ELLS5K$LfMg&;QNr zH~G5r#o>uXdv6?;+o|_yqD$a~@UuK7S3MN>-xmIurs^DYsX)o>?EE7#r3!14i;~&+ z`P9!{%HrL#)l%x^#j>i4F;AX*dg_LnXMB*dnqe0r-X8LDnR-BHwSvC=|NRTQz1(N~ t*RJ-oJFMxJAmOyySaa6_&7H!Xmpi&F9j#CPUG`S~OG5JfOS++q-2kabP%!`i literal 0 HcmV?d00001 diff --git a/assets/roots/5588e1fad8ea116388ec542a0110b63fb5c826e6-0.der b/assets/roots/5588e1fad8ea116388ec542a0110b63fb5c826e6-0.der new file mode 100644 index 0000000000000000000000000000000000000000..ecd8c9d212139600c4a68fd2be12a6c50275a4df GIT binary patch literal 1421 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 literal 0 HcmV?d00001 diff --git a/assets/roots/62fbb4c96c7627f44989f56d3795e701ecff86ed-0.der b/assets/roots/62fbb4c96c7627f44989f56d3795e701ecff86ed-0.der new file mode 100644 index 0000000000000000000000000000000000000000..3492b9555d8a927fc048accbdfd510d973a4bf8a GIT binary patch literal 1415 zcmXqLVr@2PV$NQ`%*4pV#OL~KH*>S`;nq}-Xa8LT4S3l&wc0$|zVk9N@~|=(_!ue} z$gwepvTzHFyXWL5CFTTYrspXH<>!|uI6ErnDg>Ds3L6N5RB#D%AymKwnUMtzrbf z7RhT}D$uKbzGw38+~7wZH9Z3C7XQ;Wl_%Ccc-3RGYRi&upQm~WvFHBn+0=Rd!h*!| z6A>jFt|(i5kF`3r==NOBd$BIM6FxSc4WIO@ZchM9vdR7I$rAgr%{Ey+S)*dM{loih zGQ#1Lx82+H<5BDO;zc`5SGj(AFxPBhen{8HqOd%@j-!E2E}~Q3Ek0^7ZML6Z6u0|l zHZ#w1gFS`jPnW$>4}38Fd-9e>uasbWK_l0SS*5b`tlC%^=G|_%ruy>^>pb;H zpEAqnS3j37ywS{m@&A1T!JnWsAklyin0{sX85#exumCd!n*l$FFAUt}2QM&?2T3clNEnDUU{`>etANRnk)c`j?Td?lrv{y!cYtNV z#LQ-Z{64y*oN&oy5m`>wTczfjL3M=6i0;sxcI$G64QH=U6C z7a^^oy&`9ATM4e8`Et>2H?s`?&tUywgCL-KSq* zf7`U1Go!Z543}AQX18c(@Gc!z{wGJ~Np6%dx#}}_*W2&%>s6+*RIFg${jTxpt@xU5 zuZ0szRF`kI^FE;yw)gmn_ZE+0CVi;XtY?ygew1xBAHBZk2sXLxY73M8i@v-TJt6k1Drt4hk L&8$%;mk0m=(y?My literal 0 HcmV?d00001 diff --git a/assets/roots/6435ac18333cfe8ca49f61b4f2151d005ee86ce9-0.der b/assets/roots/6435ac18333cfe8ca49f61b4f2151d005ee86ce9-0.der new file mode 100644 index 0000000000000000000000000000000000000000..7a991b766c37179da1de553d53c9b6d9b0c0190b GIT binary patch literal 442 zcmXqLV%%oX#2CGRnTe5!Nti8X{?(G{W@+};+_P@Jm{-R9$;*I?jYF%=%;uPzSe2h=C}|)L5?~e!|uI6EpB8_0?C z8k!oI8kre@L6kVJi2)Lq5~CvxgxT1^Ze(JFI+mG{o!NvPvMpuV6tNOBFYb%~JOZ2mNz<8|7- z#ZCqe2K+!T$qF+v{%2t|U{dSkN_}D7~2eFL3}iABF2F1+wtrXpdP^UR|= qjsTU+VS4{@%AC#9e!f1Y^<$gPKdT8MV%K|;RXk+w8`NISum%7{D~MeH literal 0 HcmV?d00001 diff --git a/assets/roots/64f5c98fa750327769f72ce76ccfd9ea956526f3-0.der b/assets/roots/64f5c98fa750327769f72ce76ccfd9ea956526f3-0.der new file mode 100644 index 0000000000000000000000000000000000000000..fa25f67b95edf5b89eae8022b0f367de2e636cbb GIT binary patch literal 626 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 literal 0 HcmV?d00001 diff --git a/assets/roots/7036af3c2bea54342640b3ff78c70768e0fdfd68-0.der b/assets/roots/7036af3c2bea54342640b3ff78c70768e0fdfd68-0.der new file mode 100644 index 0000000000000000000000000000000000000000..316a530a8126dfde6b2a51cf3bf0a21ff3308aee GIT binary patch literal 886 zcmXqLVlFahVv1V8%*4pVBx2VU%l;APMp`!)X>1t(!kur*w`RSoYxqcOA9NT z7?qGc#>mRR+{DPwV9>07hYY~s1f6c8*c&H8osqivqT(gi|Wd<%A-yIy47IeB?Qi;TKw zk>}R`)>G#lJadBa%?rLRu4&IVFKd5#?I)`??{tR(n_X*4qvl-ooy){^$$NI1w;Q{7 z>e~4^9SnDrIv+etxstxeurkqR_bJD-F9Q=EDi(PzEL+ zU??*(Y%Tu5d?zwmw(R9%t?CIHn|u!#`>&d{@yd=T>i<_xn|aRdL2ixDB8w$#1s}dR z3cj&-@3k*#|7rbqNxpV4!-M~pJG0pNUdzgSTU0dRbd#-MQ}?Ca^R?D1K1*(!TPgYe z`)(f735UDh`tyH1BXo0#-R=c{6sE@etLY0WH~W>c^Y5R1af|Fr0hWKK68abU>{xsL z;ROD(>$r}`R&NiubS3fERngnm=eGxjyk7eF^~u%+stV@6`_A#0v^=zF+*I!8ytE^} zEq$Twvn?@++w5-t+pckR5&OEWr%v^13B8~G_K?AnlUh~3Kl>_6&onk!p!anpxBZpa Uhv!@7KaxBY-*rtv?s4&U0COv1=l}o! literal 0 HcmV?d00001 diff --git a/assets/roots/704b80e92c15138ab8bef8cad0f2ed793ab3697c-0.der b/assets/roots/704b80e92c15138ab8bef8cad0f2ed793ab3697c-0.der new file mode 100644 index 0000000000000000000000000000000000000000..235e1868581921cf51a8c6a0ef2eb6b70e80804b GIT binary patch literal 594 zcmXqLV)8R+V!XV7nTe5!iOc32`^t<0zFXZb23%|$T5TTZY+0C@4YCcn4LI4DLs{5_ znLParg$)Ej9ByH*)ZF0A^gM+CZ%;#217(mPm#|E#Z)r+KQKCXfYH~(ieolURW@@p5 zPiAgrNotCrf`J@J8MClBRIjVEvqDgQeu;v!qk^u2yRm_sIIp3Ffsvtwp@p%5fkBiw zuZb}dm%ffEGZ0{72ZsX_BO9xBBMXBPa}ooK@=~51xNC?UzDZN$}-QY$w3;$kNQ2LoAPK*{p4h_Q&@!AUpdn?yYZa3 zjGm_49FJrt13r*6KO^IR78YRSu^I4#_`)DQs{u2RLXJV^Oa_BACPRjOpA*kB3CN~Q zW#1u@*U`$V-^?LC;go!r&kJ7Zs*P)}HgdYZw_d}+QWGGk66 u+h=8rPSq!VB;4!`><+d5-BbNKmGkhAX=mM6G<^~aEmx@%5}3-OvlsvcthZ|b literal 0 HcmV?d00001 diff --git a/assets/roots/71719c5ecfe502b0b8bdccfe3aac3b0057a13809-0.der b/assets/roots/71719c5ecfe502b0b8bdccfe3aac3b0057a13809-0.der new file mode 100644 index 0000000000000000000000000000000000000000..22d99a48c2907f0ddca24589b046a8bc17329a21 GIT binary patch literal 1384 zcmXqLVofneP+`qM1g>D?uTC~J~myJ`a&7|?#4y`zYPr7NxTclIME#{W$h8@#Sd_V1vg4^ELSE}vpYuqhZ zR!Xl5x-V>gSya?-Z+y-AwaLdT?{HLKUGVgKbm^Lg(3z7GXP?y)uf5&>Woy*`Kz>doD@f3p6<7`y&cZyWj| z9^MW=-)d&Dsc;Er&!kR{4v&ux$r78Qd!im6zAV0Kwd7(Q=_gl@JxZ&(-XyuqT1-d3 zusz;&-qhmBhnyM}g{NJgyL)nm=&!j;WP3g}K8Rr3dve?3Z=JE7Ki9Y{Je~bZ?BnqO zTa{bi4?k(Y8urnDg269W&d6IP=N<^lu+%VInccR;Vb9GwKH+uiwi|DYD|?vQop!(X z)b$zb&za5WnbSOzx#08qlDEhB%%1OkbFg^%l&I5_Qks@A94uIvr=$5WBK%EM&CG1o zB@?U~z9`I{b?&(C&rRN%Q=4C!_AdAISkCVwIq|~R{Q?gPnV1|9**RDxDJJ)?-J$cL3^_-`I(G8iwR)t=JJoCYsfNyXT4z&#+!lMlIq8AK z@roI{KfiGb`n_^V({4)wv@vHPgS@y}-dZqFjh)(d!^XIiHg&&K-S! z_~P0J*~|zF#ld{^G zSw-oAidAQ9T35VM%(A{6WwX>|-mC*U_Z#MDKDb{#<>I0Sr~g?#(SjGgo?WzW-I>6c z!&gJ9dDdOq$yVO*=smks#+k^#wRJJ+**kAPVt0z(!N?k6>HB2&jKv#`-rQV#Eq{vg zf9vgiHoHS)+~#Hp1F@XWp+aXEP~p@h^JP*z$8{iOIb(|4CZG8&xfD z+x&lOw~X&;_PqbM-{_Y#-|yS9dF4G3-~Wb-U5izIP1QUjG@WIU#kSrJAI$%pP^`D# htRijOop;7}gBb5L*=BjS1GD!!&wPGln%=qilK^IlV2J<# literal 0 HcmV?d00001 diff --git a/assets/roots/724dba45ab521e8115a29d695c1fc06daee1671c-0.der b/assets/roots/724dba45ab521e8115a29d695c1fc06daee1671c-0.der new file mode 100644 index 0000000000000000000000000000000000000000..63ce66e99ffbe872e5924b5951664d971a25cf2f GIT binary patch literal 1523 zcmXqLVtsGW#C&}LGZP~d69@0*$9r;qW^Xj$W#iOp^Jx3d%gD&h%3#pgVkl%Fz{VWP z!Ya(^Zf0y`VQy+@V90I22@+!yW^xTSR5wrsakzx#9R1uB97{{`i!xIZQxsBC6`WIx zN;1U43!KNK%&gTk|5FG)a25lR3KGUmRh6` zl%HP$RA?Y4&TD9CU}<1tXkcJqY!M~SYiwy?VQc{94i;xNF)1MjAR{XSa}yIkgFzD$ z7gG}xBg5_2+1k%iLZ&uNSX{J3l5y@F?#B3H&t1#cd}i^hp2qz~@Imx4nRC*Yc$;Ok zcg&Nmb2UofXMb59Q8z#9M55m|wSzvr|9#$@Y?4pCv9sdq+LeVn-}^_%F1=TxFn!_M z44-?G#J%^d{+~K)-f#Dt;YFW07p^OF5zd>V>tea*=W**L5w?zdw;b|Ze1iAVbba>J zAcp06C)Q`Y`64oHTey7KVaxg2xlcaZeKSqD#Urz@#arLo`;m^y+}z0yueU1q8J)^^ zy}Y8cTJF5uuW0A)vu9WEXPx4S+d0p@|HI8~T-*_<;>J6bpOj11p7zq4RPC=XP`dS3pmESb2T=wV$n-9~!H8C@1a_b9pE&Eb`wY`Kl($n?Z zhr$?3&1?I;l)JyiGbUeZFV@)V&Gq%i>L#7KVpStq?qn|UMuz{}%Hp1H+30Xearf1R zz^5KT={lDZJK1^p&pDMJa4DYS60qR50GIAmna1@`WS)x1eKd1qWYVuKUVhK%jL*wX z-mvfUCthJbmUFWI_6?Z_4l`oYX3l8H)BVqQ^xSDT`87<(oS&^*)M9hgPtG>?q=?UB zJ;&XrEcct)Xi2VeZ1-Dw&fBQ+?gYL47i3ak(BrmJXB4HrbATlTZLQeAH z_{pax&)`_w)4o(E=AgNOEJy(#ix>-bX+DrNKO^IR78YOzZZqHq@r6NrRs&`rg`5n5 z`52fC85#WKc6J?GFvC9YaOQ!m7|)-xIce{5|F=Lv3lHH~vbyu^uzG082&-`y?YPU4R zD=5-C;r6?K6@v9%cSI@^wq>U<-xh6txM<%u<>>(*IE~Gg7fw_Cywj=nwuQY;#F`gQ z5BY9fw)W9$dfBX(ZFuvpdz*jm^oe@@k2DHR#V&p-{->(V?yi!oe&WZso7sPa48u4M z1Us#tbHTXo58DSr7iN)<5wAXF@vaoy)V%Mg)PD9?k#`n%n7*G=W@O{brgTp9>D_rZ z`fuI4bj0$hc;k5s_t*6uxslTM=R1k4t2@79Lt5%B^Y!yr*}l@)cJHcTgjiGbtKE{N cOvO^CJfsvSPvGjC+_JpZT3KRJ=Yrlf05a}k{{R30 literal 0 HcmV?d00001 diff --git a/assets/roots/72c74580524922e9a5b62e238cb16cd887402dde-0.der b/assets/roots/72c74580524922e9a5b62e238cb16cd887402dde-0.der new file mode 100644 index 0000000000000000000000000000000000000000..47d3400beb8045c2698e334efc202e160d3b205f GIT binary patch literal 1373 zcmXqLVvRItVs>1>%*4n9L*4Ezka4LI4DLs{5_nf&|>Weuc3 z94;Oar_#!T#NuKF$6#Gc3u8kwV`C$8Lj?mlkTf%oI853(2c*bIAt*n;M8Vn7Ku(<3 z(7@2Z$jrdP*uua#N}SgOnM-y1nwXT3y~N1Mz}&>d&tTBR#KqLa#K>^{c-;4c>{rTv zJ1c(q@-_3xJ0r&1i`(R%MDp%>UfLY=`jt$gsK;(u_ESCUHr$yu_toKqNo`FU#dE^g z{(Gl<;L$So-1_u9PtCiJUb~A8Qa@kSZ{Ak-`1#j)s}p`+{y2A1N!JsZX=|bzUT*zj z*uFUM&+)p2`F+9VSEe5fHJ)TAp@jEsPe$;eKTfo%f_lz2<4@^;M@; zpWn~sy3Ox9*>A;yv(%X)0^NsLgxtCKcS@vJPXK`;!@S9ATMRiB|pGX(1 z{j})k@9$zMX_@mq0~b9Kl}Kmvs=u1E)pxV;gT&Xd`(DoAH@YtCZKD^(7&MXd)BRVy zH`pAKR|!7(ci4QMNRa)PM`u1IPQLgzJ^Omly8Rie9h?`0n+8{W2>y}i#GZ2Odj4!5 z3*O{4N$btJ7Zr2yepTFCvdZ^W5wqv*BzB&F(%!~|-i=7M{4ETX5S5}yj@jnZz0W**?kOc|wv52vVoNOq6kTj|3 ztJ|EP*^@!*KHb zIfeIZpQg=wxum4NO6e;ES)#Y`L@^Q__xAj;~+h9Mf(J?<^!lM^Ie=8NsTWEbzc`o?J8 zfmLh1U##+deTDsz$9JW^4NK-{o$K4QDdEdvr3=chr$kj{-sRu#5WXfmGT`RonGf^+ z^)gnfD~sNnG`E`5H|n{!he4FYGp^ic{SPkfes;emPqFD>hNbV884+noXS^oF9sZ@x zvawRMWagQDYZdOPCCI!E3tYDN)sw~c%l{|d`55J!W2Ef-Zq15!@4SRKZDI=#?W_EC zs69iL`)^F<8MX_vYC~pfowSsaT<@*r{5d%`D∓W7#JwuU+7}FmM0$M$V5XkE`*n JnIRUo008NES5E){ literal 0 HcmV?d00001 diff --git a/assets/roots/79e688bd141d71bbcfed90e833ca29c89162f156-0.der b/assets/roots/79e688bd141d71bbcfed90e833ca29c89162f156-0.der new file mode 100644 index 0000000000000000000000000000000000000000..24e5adbb0d859d830f2c27e62342850b2b35399a GIT binary patch literal 1373 zcmXqLVvRItVs>1>%*4n9L*4Ezka4LI4DLs{5_nf&|>Weuc3 z94;Oar_#!T#NuKF$6#Gc3u8kwV`C$8Lj?mlkTf%oI853(2c*bYAt*n;M8Vn7Ku(<3 z(7@2Z$jrdP$imbjN}SgOnM-y1nwXT3y~N1Mz}&>d&tTBR#KqLa#K^Gp7S~j775ue>tg;F2s6Ci_|>?|`&#G^)?V+-4PQ5Fn8k=32`u)_=Kg5H zep}q~`W=nx?631b>mBqL*An=A=G2SVf3sKX&6=;l>a@=X;-<|xeB}M%n(S_?%KFWo=WQQ#&R<~Q-MVwm z6qU;U+U%9C8T{H8KHic!ow?(bXHxEo3&}}$6Zj)m^)KARx-@OkdFirEEh!s)EQ1ah z?E28-&9q_Lb|z*<2FArs1`Y=Nz?3U1%*gnkh1Gx=NEyh21o&9QSVY`+oc;ea|J{#G zZ}k26eyVQhv&lYezz34%2dQTP<{dWVR0hmAz*NS_z@VV)Xv#9-kV8_!`$wie*X`Fl zGMTVrVSd>!F|j2c<~lDY9QtJQgKPec#;3cZZ>j1mSsq%bV)X3<_oqEsKPIo)#C|A# z{_Qg-x>oJW%75r7aK`X@!|vTDZY<(U34gWd>!Fo~qL&o1-uzKw)HvU^qd{)v8NsDC zpTsu1&;4_$LGj4x3+@Gu3r{sma_HoKiY=ehB_e#qOlH!Dr)=hTmOYy=+oC@vTiUJ7 z*(u(Rkv%vhHnsM9SMkDGAyM+0&LZlETKTtjR7+V;*)E%~eCwR&nwjBIvnN+M8%Q%> zPdeeSY}KEXCtlYl1~aZu=*XKB@3QlBLZiu>{L8hsJmOz(nXpH9r408PnX8L36dG8r z{n^;BzuU`@*Q-tD^&>M)wXe5s9Qjn5c)o63S+ZA^L+a%AA94*nZBN^-_4EA+)=79F zmMFEz&bj)mWkN{S*H4{lj2X=yi;Q2KWSO{0&_vMg)dgosdFIIzc>V|4hJSI)dw6=m zzSk#{&*q+a^22OZ#@x4iW2YCm@NVNXz4MSKcK3q7DW~hiUJL3kp5%JMr|{;UlQV%%ZK)2w-M7YFQ+6NTy+5_$Qd{MJPiv`Wo)7mb-kkX4>mzlqN%P0S zs9SH}JyVZ+;d)`NOyG)))h>@ObZ0&OVZHl~@AZ^Vf%~4VS{?P$@EzNp;{C!&T=S3b GoDKkgi$p^J literal 0 HcmV?d00001 diff --git a/assets/roots/79eabcd4a06fa193dda974a7c6cf184c1fbb4f14-0.der b/assets/roots/79eabcd4a06fa193dda974a7c6cf184c1fbb4f14-0.der new file mode 100644 index 0000000000000000000000000000000000000000..e99af2e398cefbd29f07369b15f3b7fc451d8635 GIT binary patch literal 1420 zcmXqLV(lr?GT*%JyCynJfq{o5|3-$iocx%**=NJc7jCf@#}+Nu6Aa(g zB)Vu$f9{5vURkfWUpS|w!mpAX%)qiiZEvkCG^mnA+^4C*)lq@SB z>T>j-*)ws6<9CsR$}11Ao#pDXdDC^Bv&S?a1qiH*Io;GJE@4(X{aMGX4ZbFvUvzdx zrD<&tpFK~mY_3A zQ)8QOw16=sw=C`9#M^o72NE0fHtFg26fD1JZL@jele}$5tP4KnOq$Ml*1cBZz~|lb z_^)-ZW8W6Ve~z2Sefd+n9eZn!&o*6oX%^A4HGpMG-biBAd#o8HV7ynl%4x0Ln8w+ge~r>0C2;MIC` zui&FZdQ(s0y&Zcl>d*Pje?j}LblJ`)mZ3j>ER@}`s)50$>G`I5&K-LU7U=VS4Vu^Y zebQzw23dxX>1+=+vfn(;ACp>ZlN%ZSF+!xhwY+jw zQza8KBLm}NCj$p?VwUA&5n~Z~bkbqI@3|MV7S^2Svrj#$*^wm~XuuCl>$1X(jQ?3! z4VZzH0UtRMPv^{nBddYriYf{LE^9zMS-u<=$V< z$`hx-}zrotGK|;V1Lvn{mP{edwFjD+>~tIW1iYtkQEcnS+sL)vdzZ3 zKmHi)H*b5gyKjbi-7a~thXsP4nD4758L6vD7`4p*&tN6IYnJa9{~aO)N*hmUPdKM} zu;7dS-1<)wQu5#1+&ZXcMQkUg#__49hsl+0+ zSpD6xnjL-Dms(w)v2ae0K|zz7;f2y~zPh)3o@Z!Yb+uk)?UOx!|DMTfe!;V1yJHUf-Muz#)AcU_0Q?0~ivR!s literal 0 HcmV?d00001 diff --git a/assets/roots/7e63ed24cfd532db5d6e66acee1149168947a20d-0.der b/assets/roots/7e63ed24cfd532db5d6e66acee1149168947a20d-0.der new file mode 100644 index 0000000000000000000000000000000000000000..70cd46259ebc05593fc614b0ee023af442cd756f GIT binary patch literal 1386 zcmXqLVoft>V)k3W%*4pVB*5`=OT~&`b+@vN%{%YaobNK=W#iOp^Jx3d%gD&X%3$DY z$Zf#M#vIDRCd?EXY$$FZ3gU1H^SNZEXF8`AmFOsV<|XSHsv0PRB$$O|U=j);KEVn> z!Hy;dmSze;`S~RZ?xqHE;=D$N28M>F24D~+&TC=@7Vb zFgG#rGZ-{6aWOS9F*0nv@a1Hs;M~7|(i)$ix%y}fJ&+o1O$?kjJVD2kU z?wNaY!f#1uaVYJ^aVYZabhG*@he<{@qwi#ziT%VYMdFaD#xX1n&@ zlG)QtpW-~%x4xi&_lng;^A9<5XCDxm9P$3F zqebZ+Vdkkd^^ap-Ugf>|s74@ZRrUwlvd{L5Uu-z|_x-AH-!*N^CAMfD)ZyEovQM+; z29xgD82wN6??u1xh_2z<@bNOQf0l3gbjAfA)3$Fv_x{|QH%m)Tt$F+~`TLHAEGPC< z+sEpPDTTfD@cg%2ztTW;_O5JQ`?=Of#GlLR{r+@**`LQEI@4w-_H3E>QZk|WlYur{ zalq9X)@(12ckv|jX#5D8o%isY?`}4g+1Itz?Eh-%7k<5_vajB79)IqOet+M^_Ww;^ z-S>=h>~H(|vAoh-dduQC{oig(7t(~|;#&RB-6-g&KF7Fq+VtyZYyUR=IHYp$6r-ho z@1lbu4)Sx-uJUlYUHf$5qDNVnmGkLsT!NOndW3V>w$3~Mnu(c_fpM{ufrEi8F!jpv zv52vV1RBe{HFH=mep&TwX2z%We#&;69S!(E()^5!|5;do`KHZ)AH)|1@mUR+ffRBo z17;gwDr00wShhYoKKxeaZSR1UN0dz!|6fQnXqmX(Uf{fH-^u-MCN~c%s>u{c7RfC> z+5T^(bILTU6TvK8+Z=Zvac8(WUqZrq6W?c(S1RA{u2)D{H%TMj?0M{yd&g~V2UxD% zZL8rst)}c)^p}ITMV79XOk$UCh@U?3&Ya_`w+8Y)%M1=ZV_AFas9 z#Gi^kn^|knp=f^AK-_l6r#&l57dYRs>-3(mE|>X%%;E*pICs}B-mWF!;_b$6lKj?i zC(FCVvuCY)W?1p%l}e)Z>WGy8yW7>1R2%Q#S*ZW*?ehOs{egN0KbA0FYPF1KoH;E& zD!M?@?O$7h-P#5L&byYX`VsZJFO_`w?k4YM>2)Lc!e>3}!lhU8|9nn=!rp&ZP4Z!H z^smK}1!uoJVRbk(W7edHL1h*%9`4%v+yCnO&CflW4L>v~KHj!(`Re@l^Y8ndXWWsL zrEL40)3-so+F;4yyX^HQ`uS%1B>b)}PV*c-+2#>r7UucqDf zJhhPH({$_H*(?$&E`2qOpYEvMh-;g*(en1!iAGr$mlbp^`hIVTYpdD{xt>_f=o8*I fW@RzQ6?|&-&+*RCKNa$==a$jG!`aRD7RTxUxISQr^biSrr(xdvuX z?hx`x6QdGxG%&IMYdgm7NTZZtamfjR~oWi(1?#;K}kN?-E{8N~9J?8blW%3*;i@VE0*UDLI zBP=(t!*+4N}r-IAiMKQddt&Ro9olgaXx z+m-ptauPdk`b`Tk(ONXFkhk>;NX>k2@vSdaeD}=%74B|95GP zzJ_;_N@ElE*Tj!9c{>sfJ6IGb-=lN5Wy0a8 z{$1z4EKgnfdd9`ytef0QMU}JiTayhMK3ttAuPJjVXG1__?1X6R6}$%L_t(~+`#86G z?@6~jg=Iez19vIvSMOUYn!NDJ)b@|_c7E>sY}>kH$L)PvcWVB;WzNdrIfc2!<@tVR z!}F!xsu#a3OZhtK+iKR=wl9}*nk!xnGWy)Djght}`7Z-w_=)aD&|#yfF~Ug^|^ Zz$|ZrWgC(fPX0Li#KFfVt*T)U~Y4K!A-M><=bJ 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 literal 0 HcmV?d00001 diff --git a/assets/roots/887da4445e67ea7c94774e43189c3ecee4c87312-0.der b/assets/roots/887da4445e67ea7c94774e43189c3ecee4c87312-0.der new file mode 100644 index 0000000000000000000000000000000000000000..42f3c1b9ea851d77a1b156995ee2a5296e08767b GIT binary patch literal 1011 zcmXqLVt#MX#B_ZDGZP~d6C;BGFB_*;n@8JsUPeZ4RtAH{8HU^joNUaYENsF|p}~d% z27Dk6hcLTiQD#+sUZSC}fgnhPU6?C4IlrW&I3+PB)lk(y86?OhEE8OkSd^BTnvI#U`uuza;ap9t6a2L{-?Pgs zl5d(Nte71a!_crvKeGN?`%hKpw&$7BL9-@2EPQ-Jm)9@<+X>H`+dfN$t<&CCBJ%qD zZ?(r#yJV;7Wm!i|>2weKuexAzL2&b}mJ*S}>GN1JtnT+YM9F&jop+vS{XFa%L&52w zch$A)te)>N{OS7bP{MiPNG9!uGbZl$cix+jn#*)vXX3JD&+oNLc-Ex(Ew;*Cx%Hii z-Y#C|9meeDD+ZAek_f&-n9TI)D_j}ew*jYqV9Z;Uo_wC|NGvNvHXns%A{7~Pv#u&3(uFc z-8&&E+u?A&)=l=m-0_F!{wv=;_NzD4Zza=ncR@G1FMsz|YpU&DnV4j*u}xR&n?PI2 z$%5sV)^67!V7OH{5j|q J(7RD%7XbdRdH(a+>1r8-Ysu9sFU+jzdgu+i;Y98&EuRc3p2BUzah5) zCmVAp3!5-gXt1HCfjWr8C9L3?SCU$kms+9_oSIx(lvz@#5R_V+npl*aq2OMWUs_-& zY9I_!$}G&|864!U5R{)^q7Y$ZAScdiWME)vU}69UQR2KnmZhP&p&5uDMSTm)30&A6SaOKU4F2Dcb zm*X4DJEf;G*m>k%Ep{?+FyI4*fGj^F<9`+wU|6#m@PqimAU>-BGmtWn1qtx6h_Q&& zI89r3&)dWL*Q6iwdd}x|uPid3iX0Qn84L!gOa|4Jer^FRhg{m-q+JXCN6ftUORkyhaaL*5ioY0ev2kd%d7QIlVP-ZcG2}Mj zWMd9xVH0Kw@G(?0kOy(Nge4q{i&K;H6%BRN(6*C$u*nB^|nZ*~L&^&+3GjV;X&9O|;oys5OR2D&Gz86uMaJDGH@{92L_d_FeBrC z7FGjhAY~v665wMIV-e|PODj<+v-;fmeb2SM?zQ@R1me97_(0P9AoVQ3SY|_xQ|1f? zgH$F1=1|>b&m@l2ygwO`cTk;wPW$@2tI`}2dnaigd_QGk#U(45(9Wc%(wuU;QnEjm zZMni^$gst!W#<3Y&*%M~I9)yV&W31%!gFyJQdu`upTGK^I4SJMt6OG=+Eb_L6{+gi J-#;vT5&*>n$}a!_ literal 0 HcmV?d00001 diff --git a/assets/roots/8df6bca2fa8dd4f6a68eaf461bf2091e9aa77576-0.der b/assets/roots/8df6bca2fa8dd4f6a68eaf461bf2091e9aa77576-0.der new file mode 100644 index 0000000000000000000000000000000000000000..1a0c2e285ff94f867385bb83894e2d0b2cac502f GIT binary patch literal 1380 zcmXqLVofk;V)j_T%*4pVBvKI(ueVl5()+PFyQzt=#@1<9QYRbmvT19KA-KZ8LN z6Bknx6C=ZdeE|Y}FF)ltY+;$xy3_E~9o;8_D_Lx$Lk~WWFpyQ*a`=7gy&j#12W{^+ zJ@a4d-k$R&+Vh~-)!qGX+Sk-K&1!pN%RDoP@sjDhHMc$lpG>p4vWAKAmda)86-;jH zm&iQ+Q5;dW{Ttr|-mM?bpZWFkn9tM0Z3bMu;&rC4kKW(SH|Ica`{NFa*7RR?$5%fP zp2I3J@yE-H;%bx9qO!A?CEZ zpII(CJo4*q`;z~(d4YOM9LvG46ZRU|9(lhabC1RBB}=AVOuChMfN|Qo(?!;>XzTff~ry0QD>QvoC2_-+L&8&7%T)=1vlr#VZV zGmC#`7uJ5bf5fNu*kZjCQ!7swFY5F2RrwwFgk@im^fGl_�MI*JC&pw%1SD>Hm>e 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 literal 0 HcmV?d00001 diff --git a/assets/roots/953e4462a2852855675a4a4f8a790c5e904cfa5a-0.der b/assets/roots/953e4462a2852855675a4a4f8a790c5e904cfa5a-0.der new file mode 100644 index 0000000000000000000000000000000000000000..09438c828e164c612e3680d993e4b9b6c9299d90 GIT binary patch literal 1370 zcmXqLVhuBBVzyhr%*4pVB;x$IQ255?NRim>=X(B{{>|UR+Ush-%f_kI=F#?@mywa1 zmBGN-klTQhjX9KsO_<5q&rrrd3dG?O7S0SQDlK--$jnPraL(7$@hM5sGZZrr0m(88 z^Ok{R6@v2fOB9?P4dldm4J{1JjSUUVjZ6$oqr`bljFGt1v80Jf3EAYjH&@{@k#v7Y3pUGo=nZ`eF5W|!a&p_e?n&*$Ydv}=km>-4J3I+4=6 zHEWHqj`7F&cXSeMr0feqXRl=lsz1-N^PkzTIS0hnCHF8MnZ|u@pKm23oXgIHNoAsE8x_(>j2ladZpVb+AJm;2+ zxTvlj%opyJdj0pJhJ-&pdrX5Fv!)j{t^X)n@M6x__i=}Ej=R@IO=(s0wchpG=fwuj zL#c=6&gGo#w{<$}ny+jNzMHYueVMQ+`^c7`DX(5iah>2@@mBrhmFJsxu5MDApl+-6 zqGrPTJ^p_maj7i7m#VTP_SJzhgUjUsKX_SYPcdUGKE)QX?6UQpUo39Ip1->$1$9`$)dF-UTL zWW9NA;>PcJf^clA-o)ENZGt<hYdNE0ka4&l`%4C z?waJeeC>yi1&5zZ|1f{0>ecLdCjXvErF^r>4%#G8AvNFUR@bdJGUh=KoV87%RfF=L7a}``&VDho_y07Oh!Hb)3ylMLXn(xT| z6y4h7Jn@P*u{9khUiXEU-Hc@UwI@j4b3xW zbgcI=W!GXlRUwk=uI&)^^0ZdM!gS^Z=X&;RC`vzKc<#2zQ&r{!)#}0-PYy_H$Co%= z@;(~L!QQ^_=;8Y3({4VzqcQzjKkMVKv;9uV2Dq~ZZ#ZT7ymdk6K~`%<^UL<0kIG*w zpXf-lwzVu%bo=-wUU|psMQNoa2OYlO$e17?3uX(P@7`e%7k6~aGA6Tya`7_fA5EL9x$5tXbwLk5I|lt; zsaG^@zWU=;#kD`Al+&uMSEp_;V)PcZvt8uRqiwqA*v)hyQ-iZ*dSaVr&hz#6)3r8n zemIMdcS+jxU6*9Md~~$ByBqxrrB>dJsjlWY%D`*x@VKTrBwoKYzUsC%f_kI=F#?@mywa1mBFC#k|DPNCmVAp3!5-gNRXj~ zff$IxF3j(qnpBmlpsV1WpPZPQlWFK=-~f{061EH}Ey~WUOjU5o%*o6xPR&s$OH~L- z&Cbiu$gIid|I}s=moLW?p znU1$|aU;RgAbr6g`xFcf zkyJa{u|?TYf^1eMOa+PCR({+2&TkC(kh1 z?cuwZKi~L^GchwVFfInBVgp%VqLSrf5n~ZawLjj~cFg9kO-PLC539F9JnrEq4ER9O z{EUqMSy+IXhRuK<#1{tfSq+$h6mlp7vk)+p85y+fAAU6ITla8AjCb7mY`HAXOG+z+ zj>m=97Kf!Yv{X%g!nsr6?0hZthYaQWJWiOdXq^9KhtA1*z73bW&0}BfyviMCviD1| z=Ha($qI2pvg1aJmFL-O*Ke+k6!snllA6wl&GiS;TKmWto;@iI$y;z%Oyj3-L^@ATi zE9d_G#Jg+V&YP=_W&fPKd)?|C(`}fgb~zuLw$ru9V)OEa(`+YJ*Bwv{_Gy3ni92L! z;N+`HOL@CKPUf=<;oETIq~D!ShDKqpQhvVLI#(>sspxb4<&B2Uf0p!#f6P^0JtghO v+TZ*fd1|`#;_RH8dVU8Sm_M!e{I3m*dj5fQ47 literal 0 HcmV?d00001 diff --git a/assets/roots/9717ccde7d8296f32c79363127c7d366c7ccf94c-0.der b/assets/roots/9717ccde7d8296f32c79363127c7d366c7ccf94c-0.der new file mode 100644 index 0000000000000000000000000000000000000000..12f481ee4a2515594f587101f93cf7881798af89 GIT binary patch literal 2007 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 Wr{~)Gy{QVDP9D@QlPH@Xwh;gt1~=6J literal 0 HcmV?d00001 diff --git a/assets/roots/9bbd65196c3a9fffbf0bc4bfdb20299bf0d6df8f-0.der b/assets/roots/9bbd65196c3a9fffbf0bc4bfdb20299bf0d6df8f-0.der new file mode 100644 index 0000000000000000000000000000000000000000..58d018a29428a56232b8e2d220acca9891bc8b70 GIT binary patch literal 525 zcmXqLV&XJtV(eSM%*4pV#LL9|^gzb@WT`nt69R$^xY#(f+C0wLvM@6nxEpdCaI!In zvaks=g$5fc87P1_T*8v>`T6NNsR|)QrNt!*!Kp=MnaQce3O+v0h9U++AT`Xw-0mU4 z3PJh#B?>_%26E!OhGqt4Mn(o;5GBrQjLfB;MP&v8Z0un7GcmHUYB#blC^087uzW5l zUY^mNu+Djh>Cr}mYH!cEzYiv!dv?!lZ}!zh3Db2BcZ*&slifiQ@}CCuZZ8&XtST%zEfo8)1rWS{^NW)_wN3I~M-hbTA&DFo%` zmnb+pDi|sl85kKD$cgg;Nh3o;14|QA1A{1WUSm@z*TmE^ii$Rs83?ekgMGln$i}MO z$ikq+oW#I#?DTPsi@#gG&U)`8qVUi;JFj%@A8qEAD`k^auSZ%tre@1MGtt^z@=fU6 zi`?g{pV;`>yKr=$6)QZwf6~k^ORn1lgal1!ZLXM-p1xRyQ_{B7r1jq0gxcdgTO5OS z<~@Dqw77|p*Pw}!(|{is7P7*OjQ?3!4VZzHfhi?meqiQ%+uWs7K zu^@Z3e69f>NSYs{o&^{qYzB?TKmzhCjr$B5cN^F(uvuW$W|2`+QedU8pInrqm!ew& z3VOY~)DjS1AIgtUDvB=xhIxE)V!UBID9rVMvIdn4$`_Qj73ZWRg7l|k7NsVazp4)H(8u=WkpsTF!3`02L_kQ~&?~ literal 0 HcmV?d00001 diff --git a/assets/roots/a27cf103f952a9ba8cd636012f4adfffe82b92af-0.der b/assets/roots/a27cf103f952a9ba8cd636012f4adfffe82b92af-0.der new file mode 100644 index 0000000000000000000000000000000000000000..e4795f3bd1a3114667b5f51b8f9bd078d49506bd GIT binary patch literal 1449 zcmXqLVqI#`#N4}pnTe5!NhIYPUut<`)@ti5uUcDnZ|vMrIGNvomyJ`a&7Nu#atJyLF(@s{+S$ zkA9cD;_BVG+2>CGcHtoZ5Q&F6#ZxC9O|#BAza}}iJ@~`-kCxjPI45s7!Wk*hxpx)kNIJVNJo=*H)S z4--D{>wf9r-l(~1ed{XEh!y*j7g$Vuw)t6k#*v>|k}bRW9)4Zl`DUqpfI^9!kYll& z%FR>fbQVUM9ylTErk)tp|I_z!QDvIke9;Mtx_{qxhZQh;dD_)y8VY3|my0=7CEWeu z+1CXAGm~{%>OcO;`ZL>o?(MZ5wx7#NjjwwzInn*|t8;|^=@ZN${YzUbSfoA|f4bf- z>p1`U4e@hQbNIdf+;PbW$#~>w^G~Ox&D`d!z5b%x&%cK-$46fNKC$9>cX^iBQ_t23 zu0;j<6Jvkar8;m+&OE865S4Mzbie=dz>hZswWA-*nx|pSWyK|bu<@zhhh;l6mv&5f zmMvU=F~#0?PJ!FgMXl8lE$VlUG}xujnst1ZA)mpUXW|Z1Z@Ru^oVIE|&u8z8$=`(| z4(#~+EB)^5+FY;yMm}3rt^doU^S3HRvQC`nSl)P{)u&yc@c%q#>2*^}bsL$O85tNC zCmSRh@B>q_tS}?ve->5)W*}uC4-!yjkuVTz5OH|&-bX+DrNKgd89U_N9+PKLl72TX>H3~JVa4}C2@8%b>068A7Q?#LAXE6WoX z|2S~on33t_;Z>&mAEyYHmz&Ph4GVP1dw5JwL+<~rH|}SryNG{(;B)9d!n+g-qVjfzWB|h_O#pEL$<1{ z{^DG5+neP`{@rJGX?B{QyfX@w+86xTXq;U3n76gaMJd!?rsn0J9ilISeyy@wtY~Rk z(zz__kmaw6^V`-ue||0Ozw#u#0PdAr*^R$g#4qt&b~?47>;+eAgSVNj@%O1ArK-2q z94T%}eHrvM=C8$6rmReZqcU9K?CX@4==?nDxP8Z)`TiaZKN(p<&#d`rWO$@>D%*nk zi@oZ1H?Le$)c!BBdsbD6*j=%uNt&;%rrqgYo)LNZb@@cgAJbBL_tuNRMo;DEEt893Z-l!Y0)H6#hf2 zR&PA@HGiISZ%fF`&3Ole(ry$8d}K2}Vh-&TD9CU}9uu zU}0)*Y8EBVYhr@LrH2!mn3Rw`&dAEZ+{DDsV9>0?RgLW+d6wpr7C&L1;w&aN-6ze58g`!dwtG=;!}z8@ z?ri-f4JXE_U5}>Q3Mkfon=5nFWZ@efKCU1gq0j;q+tY_cOr2xZ{q`N$yQ*f|^FQ+v z*ScJ0n9jt^$iTQb*&xwC7MO}<`B=nQMA&A{mJhD|_;tyN%WZZGizByucTqIp2T2Pv zGX7^_HDCr(2J#>QWflnou?Flq_&^HyK{{A~xsnYz83OYkFc~s3tYXdEc31S|{>{e} zzOlJ)Xq&8hxp(IY2K(t>gVZ|;PnP_BeJbU4^n1D3UHdP-eixIuOYFqwo&A^1^c+0d zc_Z(23vBRYee{2#z4!LJOnpBZHDb=($z2~WGN(48g7;ek=gyaW(={b`Y=8Yj>~yHm zlVi2JkG%P@RmRUQegDyT>D*j*ja4iWiPy^ZE7f0M&sv@m{9$_U+y?vI2ib3@u75V$ zXx8D7$wy>34!iuWP8M{&m?f2%Rhc`%U$f7h^`KB@=CxyHh7bSzYSDKdlbsA zD;;sV(;_xoHTT7q(8Br+HJdLNdhR?Qw1ma}j_rco>y77e86@=Go*O%5#omPHNeyL< z%{)D44BR{)@myN5{&iyE`{znYE5FC2{mNOr|C<2U#|^z9&OI!<7Tdpn zWP1E($%A|o#|!(5Q^OU0&Q7mN%TV!aNZl(ilP&U7>iQkW+;{(-P|@vJthYW?{*Lve zqP&*N7ZQDV&y|U=-Sa{<4s=Ic1{Ma= M3?D_;HfzrU06Z9SF8}}l literal 0 HcmV?d00001 diff --git a/assets/roots/a4d7c276adf4f0fa5b7b3606db53807f3cd79d09-0.der b/assets/roots/a4d7c276adf4f0fa5b7b3606db53807f3cd79d09-0.der new file mode 100644 index 0000000000000000000000000000000000000000..c676be5926791d2e775d52d4670db938cc591e95 GIT binary patch literal 600 zcmXqLVhS;6V!XY8nTe5!Ng%ysZg2Sw>#W;KU;alv+JDY~i;Y98&EuRc3p2Apjv==J zCmVAp3!5;Ldyt{IfhmZ?#iQqunv;{7mzk{Kn4FlBnhRtk=A|eEr52|q7A0pWc;*$C zWR{ecWaj4;D>yqEsu(DNG&1u@dpHJpIy)+a_yjAsIy)-_<>!|G6(|@P7#SMKiSq(U zBST9=Ljxd+66ZBB1#*o+Tm#D}I=iOKK!A-M91={7Y^>UiEDTD_NenC&9RD^hn0BdZ z&3aF(3kMz?w$@nnu_!l{!M&s9&n&g`$btm;Sj1RFPD%$i3jJfbDt2+zo>}?Z7fhARJ7vHJlI91gX931)8*&^n zr!yF&Fc}EmTleFRU&n@(AC%S}3Uqj%P^kN^pXo>}bAjiQNw1EIbQCPR%)4RLDIJG7 zzh5@x&0;dpcAv$5?Bb!unp51kw^dn#cfQCro8U!0Y?-ycawZ47k`hwAwt**|IP*8$=m$8*s8QhqAB< zGld2l3L6N5INZWqsky3IqP-kydc20|b~E@AFe-_n$fqC^GHyktWK138c=v#>Z+ zsjIWILQsBwiGs7Eg06zIv4Na8uc3v3k)eg5g|UHwL6kVJi7^tFrgoPZ2(Yn(eaXbg z#;V=O!l1;Q#K7`*Y2vzSH38X|+m#H~v3+UWywRy}s^y-`L66t3|M_XU=_E5ZzV@lT zJ1wJL?!JHF-wAd{o86)L&5N^sCv9l#WGbljIO_HIrBaBJ@8sQ1JnPHnC@-tMo+x-O z_4tqae_ubXT6dXRSc*($?DOtY!+2Jqe4fsIP{EUqM zSy+Hk!)Cw_;tPZLtOm?L3ORn5GZ+j~nG6}W+|Xz0{JYI3X_vE~a7)qFb$4a%UE6vm ztUtmbO81_Ek5cog$t&vK2{|0U*6#n~ek+rKj7Z9pnrY2~b2eOzO7vdF?|GTw&%!@X fmj16_d)?r|@$&k8jqITY0SY2t!!5X07zP3WZ=$q_ literal 0 HcmV?d00001 diff --git a/assets/roots/aab38011e10133cb69cdf7f710e973cf9577abbf-0.der b/assets/roots/aab38011e10133cb69cdf7f710e973cf9577abbf-0.der new file mode 100644 index 0000000000000000000000000000000000000000..8c59853592144185df1636dbccc7933a725ec502 GIT binary patch literal 525 zcmXqLV&XJtV(eSM%*4pV#LL9|bVt)`g&(TwxwdHrTx=X#Z64=rS(up(+zq)6IN6v( zS=fY`LW2#J3=}{dE@4Uc{QUHsRE3bD(&7?@;MAhB%;eN!1s@-0LlFZZkQ!!TZugL2 zg`oWW5``dR137VCLo)+2BO?Pah!W>DM&?q_qA~*kHg>T4nHbqvwHsL&l$etkSmgbU z+l@857Pj0weOxiZvGY~fTy5{xW3oQ#d+OB!i}D}Mzqa_)8CMav?zQjh+Irq>tkeI$ zvT&|Miz|y`XrA^fwffx?7$18}?s^h3&6RWG)tregYg>f-X7%tk@ECo2puX72z`=kI z=y6$oM#ldvEWm(mGvEjDg+Y8)17;v)APW-UV-aH!Irvd+*8<&jt&Qg|f7BA%yMrtJ z?;hlkXU=3WNMkZ&`1Vj?sz`On;t67c`&&-D%Fh;`C+9XpVfOsXyf@ce)<0YGBU&$X z_x`(+zAxdqWB70pP|Kg`X=zWL#e|#h9>1@@PsHLaveyJtp`9;}=dImZmd1hf%uv}4TafyPfv$KM8YEemMT4r)$ zNoIbYf@5h(Mt%{{JOep#UPA)|BLg4-0#V|;#uf&Kh89M~rk19bQG>~;Wd;Il?BFnD zVq|00Ze(FlVoqXUky;ZHwen|%^7E>zD*3_3qHcJvK6Z86#b?EZ>+a6E#*_H>_JU64 z9jqd6!q2^nwST0z`P}y_U6l_OH`nP-zmRz3%|0b zjNe>qRNrOtxUzDwlYxVQEHF@I`B=nQM64ciwp}?dd1zW$iD%iJ2d2l8lV=(5fu#8v z8UM4e0HdGHfFHyc2Ju-9n1K{>>@#OD7^E^8n58ci*jOVRaISHmL zm^;OiNw6}eTJ~4U&Ua!?oA-Ae;ym)^$wpr$Lxz`HiS9i?UfzBZ1>df@T+92~yel_i gugc9Zk5})HEOKk?$g*;~xN^4d$;4Y1W5cr~08TI3xBvhE literal 0 HcmV?d00001 diff --git a/assets/roots/b0e31e6fe1b4e58b38cd4664dd9184b2eead11f6-0.der b/assets/roots/b0e31e6fe1b4e58b38cd4664dd9184b2eead11f6-0.der new file mode 100644 index 0000000000000000000000000000000000000000..194de84d8ce9e2bf50334b34b7c32dcb7562af4d GIT binary patch literal 1471 zcmXqLV%=@f#JqR`GZP~d6Gu3g;B==WkIxwJvTezuk8%uP)E3yXR>iTa6K+jlzH-<8df=V-pFE7!5$M}JsBr-S2*M?0Ma^Z^1_!Oae3_J_rRAiW}^D zR*}5t%cixnwKx5HXxS6Di(6sJz3S#2{~w3-ZWnxM-ScVV!PvP=xP7#5Zj$)Ab-p(f zGb01z;$(wF16g3|mgQp+V-X3u(P4FBenr~b&d=2r1yfEkF_~R3;0H+yGcx{XVKra| zQU>xM0c92m1F;6|I`}{e_(3{YfVqy(P)=-^{%y)(Ypz@f|vBZ^c_ytQ%>8EzAt~<0b$F8Rf|ki zCzl4@TRwL!UwdazVV0PUq-^t#S(>T)zSz&Y`QdX^yJBI3@975?v+Z~PdSKvtzU^|V zfTYC5=}a57i<~mnEZFN8bxUG@_Ke00e`oEB6ZJSND7y22X3Uls9pCND)@@JvE0=Vy zph7^#Bl^~!B^JBV--g=7h2Ouz?BLV5c6~>m6{oDOm0Cs*35r z<<}aFY7r#|4-_lqPM-SLvUa!D<8Mumb*9Jr$xoc}>z~fz8)Z^Ed^cJ6&;BLrkx;4j z==0x|7iPUE==ZF;_H-{7-)-H`z58E3UQ&Dd_|(D|w-(nNJ@lhw5{q=(nk7oX4%^oJ z^C>(3dy3Hk)A?ogc~2+gEm)?uXot?ei`uP!muH;V#i+e8r$lx~@zm$BZe4DLNzJ@rc*6(=~BFSC?? zHSh8K3u*bRZ&sDn?avll@Ota%&G0Q{r{~`8m8+-Zt zVglSY+wNza;lC*|#NE!Mx*|7hvR?74G>wY0vLgEz^y};G(Chmo(skzL)|%frb9t6N GoCW~9rDq}l literal 0 HcmV?d00001 diff --git a/assets/roots/b63a54aeb7c096b2c72835b60f7d423e851e811a-0.der b/assets/roots/b63a54aeb7c096b2c72835b60f7d423e851e811a-0.der new file mode 100644 index 0000000000000000000000000000000000000000..dd740abac0c9da2f5e4f611077a0d4b6026fc33c GIT binary patch literal 1431 zcmXqLVx4Tz#9Y0AnTe5!NyPaVPrRRWgM`cbi(C9n#X?chJ%$9s(IwiMi-Hv-J z*2|XN+GToSVgdX8&1|2#KIw%9R`1qPIbAX8+nI8UYW)t~e{rr&&vt2e@WTx~qUR17I zWocv0Ki{jm=~Q<9DJ46@3mYzI$xQ0%zacrt{fQmV&O>MSIr98i;?8>eptymBRpyL47i3a?@bSx{( z$oQXy)qojD8SsGw_(1|Jz+A&-AP?dzvq%_-HHhT*C3R1B-tf@j+w#MIKl1P37qwVw zAPZ8!$0EjpT^coi0h1vk!x|k;$J$>pU!|r>?AKY@wNfRd^y?0X)u&ge1ZqpzC$C&L zBTSpsS!wb1>u&Q7XbIW&O`GO$pU1d6+kB$c)ZLbvxd)hXx~6cmGxSf~2xGZb6<42yuvl$s;_N*qW*q6N@p0+m>3q)0f4LPJCj5V_c78|7#HXzN!VAiH zubcA-bWHnVw{Pm+kB4hNEIwT6&K)yJH2G~%Y1ebN%+rCP+*&?gKc)DujQO8#)EBC2 z)FjiOpEdEv={o#*=%PUo^a2?nYeb?I5T3-9RN>(0yhL2Sv}X>EFvH}>^dy>QhN z+_f(1`Of_t&-6c7@PEhVi>=j_X^SnlTBqG`*mr(=dbh_(N!8Fldwb8v-SS)ZDf8Ul zdY|$O2i;6(#3;{Za&?{(b$5^ZTBU!x;`AqKYPMA?-pfnbeww%6JbE!l*6S~Knfe2m znKte)&zKhQdPn>fjjp9iJY6jxBK5ACUrc=JR2-qz_`U4*qes5H@!z)ZW8JdOkN2LK zEpy{$wK(q`-VEpe_lWPxNzheqH&=O9m7u@%ax=>+f9KLg2b(vk&(Pk;*RbnAYn;>0 m`x_G`UVbf6S5UF8ta)Ey)P)I;PaPCfP51h+t$NqPIX3|cUR5Rl literal 0 HcmV?d00001 diff --git a/assets/roots/b7e7d918696e6d071cafa898ed1bf22538e71ab2-0.der b/assets/roots/b7e7d918696e6d071cafa898ed1bf22538e71ab2-0.der new file mode 100644 index 0000000000000000000000000000000000000000..53919a69c6d0b88f1f369a2a827704e3c49b7902 GIT binary patch literal 537 zcmXqLViGlIVw}BznTe5!Nx*i7^|CbE3CZ8<)WR)T-ZL0*v2kd%d7QIlVP-b)G1N2A zVPg(uVdhbFEXglc2q`KpE=f%Z%Fi!R029uR3a-x13L!qh3PuJ-hK9Tb+#vN_JS-sf zhTI06ATFCQlZ&f?oH(zMp@E5!k%6U=nUQgnIIjtiYiJJU8bwjrwlV_&Hg>R2m>Ahu zwHsL&l$etkSf;f_Xqa=QUs!NYMN;zN6IOQl>2F`8H0}I$q%h!I7tjCeTg!GY%$c0i zyeK1Y>CJsQ9aA08#@^j{syVs&%z=F+Hy_Mh^3KaI?7<98$H_qgS5+*SjM6_&5z2pm z{pdZtzmg4$oeUfd_<^356=r1o&%$cJ45SQXK>~a%Vk{zM8miZ7AEeEy;@p&F-DiB= z?6UtN13r*6KS(_bFjm@-juV=6SyO@cPT1dTve1X|X36M1=lYe0ew{ dyIiyCw#xZbhy9oC^a#HAQd_UkveM~^1^_(Uu?YYG literal 0 HcmV?d00001 diff --git a/assets/roots/badf8ae3f7eb508c94c1bae31e7cdc3a713d4437-0.der b/assets/roots/badf8ae3f7eb508c94c1bae31e7cdc3a713d4437-0.der new file mode 100644 index 0000000000000000000000000000000000000000..d6aa0f435805de638e5c6cac066ebb0081fac3c4 GIT binary patch literal 1415 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 literal 0 HcmV?d00001 diff --git a/assets/roots/c90a620226764c614af7a3e7549b6b1dac6b0f55-0.der b/assets/roots/c90a620226764c614af7a3e7549b6b1dac6b0f55-0.der new file mode 100644 index 0000000000000000000000000000000000000000..675cbeb1fe2031aad2cdfddc00627f9daa113bc3 GIT binary patch literal 920 zcmXqLVxD5q#8k6@nTe5!iOcZolO&#;h_}~n81S-jYPET^edlFl4 zKDi=e%UQj`((AJoj`?ct*_M&e^<8mRm}%7Kvb{IX&iX$&%zJ4h+rr5vns>!aO8q`H zZaF^9Z>^+gkk>z(g3^Ms>$O*l&N8-HwYhuMS?9lBUlsg+dtT6b$~iwHvCGO1TIHM9 z+c4_nyq&4jebX;<#-{K6jvI_B60h;bAO5w-VUlc&__G2#)8~t}A4+<(dWnV4R!h_T zSIn8etU@wcbFY2UcMg@C$jf)*YSxlz7lmzfDgs_uO}Zo=_S96$LB_6E=dI;^rw7fB zCHEJfJ=B)qk?}Z_JA|S8Nm-^MZ>66JN1Vu^GaI+By%O^;=0$%L=BeBXP)^sod^HA9%p*>1^_ zd;9o>9$Z+p(4NU;W5Kz(64yAGIo;kRJgD1t#FHhkwjg28s!jhZi>j8%+Qw`BIQ8hS z<#zX-uj}r!D6juDhmHN^@8ro14qK1qa2aZxuYT=c)#E2?_HjnD&NJX;jr6~AUW*JHvh=b&qc?2MGLHYS5 z3eJuSWkv>a;=G0yhK5F#hK8o5rY2G1ye7s-Tq@bp#H57md`4CV<|Zb727@LhE~X|X zMutrn?AiA`s=kmt^T(s|%;)FkE@9r&l)La>W2(H5`kFq5U1HU*4YC*xY%a;D+qC}p z)uhc;_5r7Q%v@BGFPXjMY5nG0cGYKLLQ(o74dbP!4R=^6DVyx5-5aLlF!$A$YUZ^W ze>OGx&$v+Xs`qK_+2q5EzulX3C_?hVwB1LKth8NPGVR~`pPj4rP8AQiQQ`8Dr(AT> zgBecCmt0$=FzM{U1)HaBvTAYat-U(+$+g=rla54et~}av+0?jse(Ay>^+b!0k+yvS z5B65tJkOc-FYYE&>XyEk3xX#1WA_IwZ)7-JKht%l=vDC(T)}UWKhLZ^lPT18Nc;oi zy!|bEE(V>Jo|JZON$}H!`;Xk_y!Na!O#8&+ntPA`cRn@E(|D1?tsJ;gmG!m9kGmUB z{mS78e7{{H$K{mT!{TF|Y>iL5)@*pBd2FZ3=4=tS~XSwp2Cgjx+xP_vUWw+cfY?a~I4N22&-)VJS~xbVO01naJkt7I0LZ-3^o#7$?)s}*HQ?%e{x>>lAck0$wU zTgQ6(&OxuZ{x!Ceeuv&Blxp8Tc*^N26EhqLJFee$L&efS7NmfWMT`ZzG#^NspONuD3kxt8u^I4#_`)DQs{u2RLQaOj zTm(#pj0_8`BbRedu`_GARHtvbblUV^f@}Yze=g+ww5hWt-DUmG_iv}YnUiwc$RIy` z)|$HbO{*Er>KcDNX^FeE#_U0M($QLPzNXwuWl0zRP5&|9phn{7mZ0(`*|OW0WmkSU zIE!I#Zt0EM=f93yES^*F!mW~dyV&z$lWgW^ZSMe z>C?k**CYmWGHWn79N&E7@0^0HV;YBBUp{&(HF?23?-`3)>{!bgr`i7AqQF0=h^b0Q z|LWphffehuv|UpDw*KXMaqw(qd!qKehH%&Y=^tlLj9a?oO{9kauf_2VzYe?PvV5%w zkeM_s`qtMSdb^^}KmCw3*IPfLSl3_oh>YEr&GxHB`K%=!yOtab7fLJi`4#*){B_b) z&UqnsFCMZ9x7lm4c1Ftf9n7&V6x-d?0q}s7}Ono9gpPZCL@ereUiEDTD_NenDk_SiBxl{yM5pKV}iV+z#ITG5!q zoqFcmskw6bUTJ}gm~YOV_-mWk#-6-;HD_;EF4I4^`&^;^)1|95-Xyc6&HVA}(_{hG zhm9IekB?=1c_!VG;MKYQZ6&|;xsTsmzA{!ESR7&yWWWauI9Yy1#{VoVz<6mh;0N)A zL3~yNW*}uC3liXC5n~ZKae8J^K@iW6XZtp>gllS@cM^DO3UoQAHX9=gW0SckBgg~> zd?~ht?^l?|l_dsZ6Dw|q^rSL&Jr77bWVqdO*A6KqcE3N_Az}*u OwYNU^;qhJOUkm_{_{g6C literal 0 HcmV?d00001 diff --git a/assets/roots/cd30d24c343a82ab1f0570158ad7a107762992e9-0.der b/assets/roots/cd30d24c343a82ab1f0570158ad7a107762992e9-0.der new file mode 100644 index 0000000000000000000000000000000000000000..2fa45b280e22a9fe641b25ffb3f7d866bebec657 GIT binary patch literal 1506 zcmXqLV!db3#C&W4GZP~dlK|u2T!TN0PX(@!-jl*-%B*X^%f_kI=F#?@mywZ&mBFB~ z!;ss6lZ`o)g-w_#G}utsKoG>?5a#krEm!bLEhlW?@yZTv2Ipi9%4Yqk?m4QAuW6 zW^!UlW`3T6V`)i7ei6_-137VCLjwaN10VqcQR2MD76yif7DmRVmZp|bgUP8)OiIWB z$;isU+{DDsV9>24f>r`tqA z?DrV|tIL`5Uu>?#-oQT7Wy{k}6s6x*Oxt*6b_Mf+0IL^gKHS+e&*$HI)@i2l;ZfWF zpZjUMU1!_3kh>?I3t9QD^Xb|iGi%Fs1=~~gN+#>-tTWOWU7j=gxdqQQPWV1$U$FUv zV&~6BhlAhWUiGiUFY|bIPr~GTM~?H{e`vJs%w+4pimB<{?`*^V@m$E{m*)Shs+Mh> z`#sP7SF!WcI=-c88#QZ;T9QLMkMJIx9AY3TyykMP`SRDXRyn3Iw;itw-CF6%eg8&7 z7w6=pzO8BJRXfhM?AU0(#FGCe)4bP0K2vKxXS1%16Kg)RBmL0};kjp`n=booejEM& z-qYeW%GSCJ`%ObjI6ZH8Mo869GJ6&{iSM&ot&Feuo|A8!)h_IJjCWY+;__wY^YHe0 z0rKG?EA0N3BV7E?r~I6|>Cbb>GTyIJ(92db)!TkcA+&naiHzi{X3 z8`~?tcH5t6y_R3UQuqE&rte>;&)RBw>u~ypcCVEh^OXDk$EasN+~EJ2{T+kemB{wf zL5@>jeR043F2ymlrZfNNdhjzY;(kN+JI(z$s<2>y5zjm6H7+upixM2hLgE>Bj zWv=~soVnHAtxTH9Z}wV-32o#c53jT?VXOxPVeQ|W8avxuYe g9fr>DI%KaO>$7prl3gxwuhv%VW75x46IT2M0J!OJIv9@X0mJp{fh)g6i}GGL9Q?v2kd%d7QIlVP-an zFyuDiWMd9xVH0L@_A^v9PzG_hgk?gCN{dSzi!&1yLQ<16^73=?(=$_xbrd}FlJyLg z3=}}hn1v;gl=%cJxH>y41m)+KC^$PB$cghBnHZQFni`mz7@C?ziSwEuxTaAww7JYc zfQ=pOLncNxR_#U>1|{Ys29_Q5OQY=>?P@=C;W!Z;= zJ$wDHoV(Hy{30&=y5Bw>`_Dql8D2I$)`~W&@t!TBIq!#V^VZjWhMu-+i7#b~x;wXV z?Ve+7%75$4y_H__Cyh5Qb~11<;0K0_tS}?ve->5)W*}uC3liXC5n~b2X${`J(fO>= zt9gst|1FxaV%g-E0tS2_X?~D;7GR9AA;%4K27^H=lR=34^~1V}Z604Lqe6L2_2YTJ zrRS78NVz@#krC~uF1uq!ZT;l^9KJ_xxf(3wkMpWvGGtghvHg>var5DWT@$T*v;#Ko mRhklO+F9IubhhNbEel^>S(vtsajDtQ8ySfj+e;oUG6VpT^t)34 literal 0 HcmV?d00001 diff --git a/assets/roots/d07c43a2a0f778fed080dff8600e49515c1d05c2-0.der b/assets/roots/d07c43a2a0f778fed080dff8600e49515c1d05c2-0.der new file mode 100644 index 0000000000000000000000000000000000000000..ff212ef71504fa5f8b59f6d643783f7f0b4ad60b GIT binary patch literal 1380 zcmXqLVofk;V)j_T%*4pVB*4Y!)WE>N=&F3eWh(;%1Cs$S8>d#AN85K^Mn-N{1_Lic zZUas>=1>+kVW!YvLm>kJ5Qj^c(=#PCFQllnxWrK1Koum$EG!2XQ*h4D%}p&z&P>cv z2+GedQE+xtFf@=8=QT7jFf=qXv@kR>GL91GHAd!A)vhKcC1kHKvNA9?G4V4PG%;~8 zH8C+VEDw;pXFt>Sim}_r{J_MfO)LHT(>r5ZZU*1w(YSg2!~bj7#eaH*X1^@7Ml*(w@C< z{EZAcyv5_fj|3n3#x%Y6W|H=P75Svz`-gXEdV5ybOit)f`ZM#E*@;7ouIPj?q?_gy z?`Mkmu;lL34U5#OPCYPNDk=8$@AmiW>>a-5MzQ=oXL3sAPKmN~tg*0A>=8!QZw7an zV&^}&>b`f&Yw3l%Jw0#Ck7oMZ^+aXDlO%utgX_EbO2k)OEb7RrV)Q(-#jWy~_{Li^ z$jch+;@5d7p^u_jjP}9d(i`V@1G?CKy?AT!? z{O-lQ|1)Ldcb$GA_i1N9!U4m}#ZTYwTkzJHyJVk&!jbfx4;;)@p$+wJe$$qJk+XaF z?FQ#V;}q@W>qc?&e;%mjugIUiXY#C{bA%>TR5i4VUN0({6IxQSao5AV0+wC=3wC83 zuKsWMq1e`Tiq=MMN4^FBd^=LJD~-2YXJTe#U|j5E;9$T9OuVxEjEw(TSb$lA&43@o z7Y6ZJ4VZzHfhSs8LF17;XtDr02eT{pq# z+cNJOmomvjIdzS*aUt(j_dMp9@Kf{px(tqoQy3!N>*>`62lZ@=VSm-JYi4)Alicd3NsLC-W&48{+3|UR1oJpLMdu*U0vxjZ&uCPyegqd zTG#q;i*0zHo)_vFENR=ihK`@rq7;-^p`VWVym=iKVJr zcz-f}Saj(N!-^}eZ8+eT3jI{aI#l0GxwX=Q^s$T{<PJ zpuzIluj~JWMV7s_o#N$Ga^-M&WbxU4w|$`&?|;3RX!L4&1dGx=i^FI3f7^c=IsinoVfxII#fbSag)S=t$RR-k2WV#4Xz zpVO6m`(`|QT>mY4_qwyEyc)CPxIYIzEYW^hr@s8=Oi#ZbOB-&h^2;PAb)7U$&$bb0 zKDPGWinC&Uy&u@k3+I1Y{m1d^!R|K<=CPbl{Tkl?+8ATOwp7~d;T;QIn>(xJCrDlO Vn)B{rLC%?XU&4P#>}ttt2LSRMTnqpJ literal 0 HcmV?d00001 diff --git a/assets/roots/d422b47d649377b28a8d0620f4ffafe3444e309c-0.der b/assets/roots/d422b47d649377b28a8d0620f4ffafe3444e309c-0.der new file mode 100644 index 0000000000000000000000000000000000000000..7a8caf9169eaa54eb64a2e7e91c472ba65c282c1 GIT binary patch literal 1386 zcmXqLVoft>V)k3W%*4pVB*4Y!)WE>N=&F3)t%iYtfyscEjZ>@5qwPB{BO^B}gMqIh zw*e;`b0`a&FjHu-p^$+9h{Gk!>6wz67gAJOTw!|uI6EpB8pw(B8k!gw8k!lJn;IJ%MTzqoBXg;3T@#ZMvWFO18JL@x_!$hE zn7Ejlm>3zhDNXsa#AE#n_iV1;>Sa$p*>_o2dFdEx_4IC7dDt*}MR{3^+O{=~PP`ni zLnC%q{n(RQ`)NUL_X_uyNo8_^?>Iy*ZgLU(^6J7bIsLfXr;8oe?P1v9rR}h3%|mPN zL%MUHES&vD%1Z77$L0%9R7~0D&)IS^DEBt|ss63eADTb=AJ<)aV(sav3QG(%!Xu)d z^h(cKJ&%{?6PK~2k|o!4z0bkrJ6&A=wwyLoXpGdIm7nY3k$B4Ie(=ThL3>>0uh!3Q zE_z?uaEr}YI`+h^AUBK+&&Lvgv69P7!KD8|Df%*E?Cc>7gFD%oZ z%&1x=G-FO=TASV4j~h~DegT}_)GTyoJ)GTmYNq0%x7^|l@td-h|M&7{7}ZHx!Ew$1J2=eC#<^5WHP=g2-u!XB=(fzB+`Xys?>kJj()8bb+noBrxVL78W2PIwvrOi% z*;_9pW}Q9debZ-x+iKCsh3+Amdd(W~GKUteE)r*8>vMh6{m9t1{+6W6lcb`<_mz%) z3cGSQernO%-1zSv%+Y_3YAyIytlT0X&G?*&nUR5Uv6F#=0Ut04%knca{%2tUW(+n1 zeh^<6#Ah{N22uvHAOSuWF&2@>g%9T4SnhQ5cHzYADU)9xUVKqxF>)#cW*cBCV`Omu zb(d>{;6#hHzOmn*@=XtsZ1QLi=~Upj;J$q(*yF|lJ!JB%cM{@hu4NB->V^{e^rO5b*UQuUna(bU*K znID2P6Q`C|a>s=}vyhpxGSYb7iNcb~`oPX@C$e@JoXD~`oHy^Gx!5b#B%f#@_qWTp zY>o&!?)kZ1D1+0m@I&qoKlk6)THhUwaFARgyZooOv-zEjDF5@X52cjc+ah}NOGt>6 zzRdOMg&`CAZYs*6fSU_!i&(yw3l4*wv`z(>i~BUY&78xh+!tJg54eHF-tN zG6qHWA1r!#>0wE!-Y21+ zz#lbFbnCyaiGM9Nf1~cjU1ugbmHVikoM4fE`@@;veR9(y?XD*HJ!92)>scx6z02da z{?DSzmZ$#7-!Liqy2atVA>-jU_XKstW94@QT#S=ko_UYmSmRW{)St^kPKUGwxUT%^ z$;8aaz_{4Sz`=kYm?&h085#exuo^G}DFa!M03VAOiwH;A+0*R*RlXfP`*!z)J&#(< z-Acb3@PVZHLF!q6sgn&klz|Bo7|M(c%T}yaI>%_cZ1Ml33m$X3 zvd6EzT1R$NgHj9FIj;L3&a)5@0_9^{1SKg{Ii zy}4??Vq?HfwxuinPc^@=>ffWyUuWvNwsbvp;=EqMr2oD1#>|&{)UUYr?&5WmDL*f= zL}up_!y|JvN|;Ul8%wG!eaTz0-ER7^vKQ*p7N^~PxbBl<^r;zP&BX;z+N3TSI_}cn zrz;#)mhbn%uaVC_nptUq(7vUN<<8qn$+*!J8%TB(rytMlE n{8`-UOs1L}9i~42`C?9JhosxJ?IvIA&RNd7Y4I`eyT1+q$_H;s literal 0 HcmV?d00001 diff --git a/assets/roots/db967dfe40104bbb8a15f44c45067d6d9ca0117a-0.der b/assets/roots/db967dfe40104bbb8a15f44c45067d6d9ca0117a-0.der new file mode 100644 index 0000000000000000000000000000000000000000..339a109cb188c78c739a75952bc6197b76cd806d GIT binary patch literal 1453 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 W8osjoY_pqF7&4Q^`)|b08D9WMkx+90 literal 0 HcmV?d00001 diff --git a/assets/roots/ddb36391a7eefac7df3e47972cdd9c04be806e9d-0.der b/assets/roots/ddb36391a7eefac7df3e47972cdd9c04be806e9d-0.der new file mode 100644 index 0000000000000000000000000000000000000000..b21a3441717a5f10cdf6efb327b8b8dd691692ad GIT binary patch literal 553 zcmXqLVp26|VqCp|nTe5!NkB)Oqc%nBz`s_BNVfA^yj~k{v2kd%d7QIlVP-Z6G2}Mj zWMd9xVH0L@_A^v7Pyunccx0ViJ-s~r+!dT%gF-yrJe?gwTooKcLp=P0JVPQ4Weuc3 z>X>;%oV=VJ72I?3lM-_jg7WiA6r3H64CKUl4J{3g3@r_e4K0ieqr`blOn_V?D3?aI zmKg}Jv4efa#K^{~-N?eA#GJ&yGWT@D#NOcatvdrYGXHq$DetJnxgjtp;+nQe|4|_~ zk)z%EIxYWo@>X2^$oZIAQRIlTZOuWQBi>nFXJ58IsQsq2@0VBL4uh`05AR6`aeZ^& z{#<;&de;?4pJ_G;(+>U>m09d$;9wvN3=Ua77BLo)OI{lT>)AeBY?AlOi(lRR$K%R| zi3a>2X<ns zd~v>d$!n&hP*}tFW|N1eejd3j{NaWM2D%$p&1JT@&BkQNaQaMp@ad}i3W3EF4y=ey oyl^zY@^9=1>+kVJ0V6LrDX15Qj@xz&$5FDKRHFGd)isuS~Z%(NM-f3M9oWEQ}--l%HRs z;A&!KAScdiXlY<tC;=DjX1ea>|lo<%Hv4dUD#K^{~-N?eA#GJ&yGKX*D`Ry;} z1(lbY{95X^e65kB(H8cV>Zhjc=DEZraQs&1hUq*7!kmnKH(t5F+PA4e`|KYL-rH;T zENL@;Tfo6k`f4(FwO-I1p#Nq0 z85#exumHoh&43@o7Y6ZJ4VZzHfh=p0F*4N9RL6T literal 0 HcmV?d00001 diff --git a/assets/roots/df22cf8b8c3b46c10d3d5c407561eabeb57f8181-0.der b/assets/roots/df22cf8b8c3b46c10d3d5c407561eabeb57f8181-0.der new file mode 100644 index 0000000000000000000000000000000000000000..99bcc84b7e68b5b28e4444f6fa21bc7c2baf497d GIT binary patch literal 1428 zcmXqLVx3^n#9Xm}nTe5!Nq}{>bojhJMWaWS?0c7&m&O?IvTn?JkswJuAWqo zP1e*s_a@Ho#N;1}iL*^!vmT3k6D_sp^~v*R*O)lOZ>&mtSAN1{MOt|H{E&z~9_{V^ z%MEUZy*pJM`*`h1|G1~7&kaxCnjCkhufO5ewuv(wCR84-IKFM;k*!%07R&;@H?Ej3 z(PORc_}XMAFtK2DXp^JS_1i4PT6q&0YZQI1>{%zxTpC-EcGJqxWtOqSeva!=o=Xlr zTe%?p?h^Gq3;iv(3Py;3SBY`!Px*c@v!iTAnQdgOQ(1fG^vo)c4-XazNvF*!Id#ul z?m1ubx@TA3Pnu*k&-M<(6Ia#FZL?e?wd)Q{*>Wi{_qFlOqxZd87|ztnOg-HHU2)SU z!R@>2KV9u9&~Z#ywJ}-3WvWzJQr)+P4ZmNcEHl2?$^LNf_GivZBz7z-XMD&%g-20# zQ;4Q&XUw*5U*l71pZrEK z{)j?gcK*iIZQcHduDQm~Rrs?|?&yL3MH}n5)MkEtlBqvKR`=`8m78RrN;5GtGB7T7 zGH@{92PS7(VMfOPEUX61K+1p*B)|_6U;*Z-HUn7@pN~b1MdZ!($4!?CV^e(Y>!sU2 z-!)^M48K2eDg$OPU@Bu|*qwN@c4f{!@gozZ4=-HA(EB(ggFozi`MQFie`k5k+~v@ z;|G_o$XKF&XYNn+bq1|Fzoq+H+4VTN((ZIU8a!-?Xwiwjs2;$JM? zvV8rD@42RPYNEQXEwY&TxuW}v?-ZoX1m*e!D1M{-ku zW1+3RZ-H_fkJp{XOJ|IxwD59pPM7gN@Ge`S#Ng5cOA~=sMNvkM7okS?3O#RXhzIyS z+vj_+bj^iRza4itFI{!{FsqBdj@j%-zaF{nP!7&v%TEujciZY?pQjO3sdj0}ilph6 zfR&$*WHWvetKHnrfA0t)=$1ze_=^}`TkG{L*Rlgt&`^}&Rl)f&LXk-+o%1iN!lfsC)s~w6 z?Tp>inj~vqxWQ7QGU@KxG3!Bhh+^5 ouE}lQ!_OaFs=4ZwaQTyaJ&lTM*#+DM*S6cTUo72o{&QL#0GZBIfB*mh literal 0 HcmV?d00001 diff --git a/assets/roots/e24e78259c056c7f870c7b62f5b043d96fab7725-0.der b/assets/roots/e24e78259c056c7f870c7b62f5b043d96fab7725-0.der new file mode 100644 index 0000000000000000000000000000000000000000..9519275a5d737dc4eda1a28957c2455192a9c7ad GIT binary patch literal 711 zcmXqLVmfTl#N@SrnTe5!iIKs8i;Y98&EuRc3p0~J<0?aL15P&PP!={}Cifsiegj?* zhh3P>u_Plkuh`JVzzHP4C2Z}Hnv;{7mzk{Kn4FlBnhRtk=A|eEr52|q7A0pWc;*$C zWR{ecWaj4;D>$bXmFOurmX>7X7iE@I!c1W%%@kK>XN92r{1RtJ1tSAPQv*41ULa|1 zU~Xt&Y;I^2CC+PNU|?o!0ObxX&y*Pmu(5;Vfr*igRlAXeL5Vqufo0MH#~0pBEisRN z7z)m8OY!4W)t~FA&$Z6fzhvsBz?AIX>}U1`Q$Mh}xGyWR2q>G%6xFv*W8P0)znSlK zJo0^1HD6pTjJ_{>u+)6Z^bJus$qhyqED-OWxOqlY{q-_u3S3jpwE3TvLk_#ZN0a^7RPMu$UE+JNwk5npaE_ GN)rJ9G~Y4+ literal 0 HcmV?d00001 diff --git a/assets/roots/e7e9ceba6df839282ba96295895cca12e56e2cd6-0.der b/assets/roots/e7e9ceba6df839282ba96295895cca12e56e2cd6-0.der new file mode 100644 index 0000000000000000000000000000000000000000..f61e2f87724716f5c058bbaff2730cefcb61e822 GIT binary patch literal 1412 zcmXqLVr?*JV$N8=%*4pVBqAem`g{i2IeLveg=akCN8EXCPs$CVmqX>mber@z2^62=lN*vgBNEteofxc?|g7K zN0e06L-sN?`ToqXegDi7F7Py;o$~7WQQa7QRxze|Tr2dSCa8r>`!T~ocFnrECF%Mh zzo#V6c&z5td~E7APUnELytVgS_!5NtqfPhjpJ|a>n{(??MzQ=)!=;h}#jIL~jU-!N znXJ0&BxlfTKX0pC;??}U3`Iq2h3r=?^DO?SC9+_L0y>s-g9j@5!b9Kp+TX(wp`2O=BUY4Myao%kIuW+t6 z(e^J5#Hx?nK5`{xfx+*me`O0N-PP=OjJU_J*6Ocqjns;b;vf7>Dk4;`wJN@@l)Z3g z_4y>54acW@;{qTCLa~f`hWbp#Gmx7?ExAd60YN>kev0HQcHn7gTsv&q(@yd=r$EU6KN{LL& z7XIUXX3{eLC4u|-A|~8yYPMV!zxh|*q|IlI`C3;hVr2~57q`FFk*A{6CN6i?{t;}z2a@Io zsb>LZF*f8>2Fy&rRL01lI6I3~Ayz_cxzLjD?)GO-S+DCm&~)yOhiZj*&VvU9N3Kn~ zdGp);syV{DgQrgVe}2+~&qviw!&g`@l6?K6Ycb2c%ozW^$8#!0s zIwi$^w(b7y%-dT$Bp7t}EIQKZs=So%p-ZY=*TF0U*+&UKtX8#iI_+$nbVs#gM(4*G z(~n7~-c|mP)c+U%<97SGS1b5=mc8I!VDlh*%8WVnYuTR3ymAtVf21aAV{ZBV`kr?? zp1x;(w^!-6Mw>~*k*NB_MHT#UpHI!*BW<(%gzmTHN`j}DZ#+-u>f$?X+Gk>U>8l9G7u_zCu@_lzX0`WHDls64%999y&Q z{YGE^E8!DNv+k^8neN$XGF{Jr<@t>7x49W79F*s>|8eM_->=)Vii^eeuB1N-P_wXE;-Qk zqiK4>tNs&Saay$<8Os@yL?)_K#mrkQUfb2CXd)3HEpYFGxwqPJ$%KZNCVLYk%^a?2 zwsYD{topV`SovO%Fo8;;R6>zEAO z?!7u4JL`?np;WOAJ_a!cpKtjUx3S$g*U92xZvNx0ap2s7_0^#_81-sdQ~26;OauV! C{id$~ literal 0 HcmV?d00001 diff --git a/assets/roots/eb25d890fe2720f41d7d6ac15eef76cf62cfab78-0.der b/assets/roots/eb25d890fe2720f41d7d6ac15eef76cf62cfab78-0.der new file mode 100644 index 0000000000000000000000000000000000000000..9e42698728dd31fa171527c1f0ba5bbf0994c2d8 GIT binary patch literal 653 zcmXqLV(K(#V&Y%G%*4pVBp~m;epM1fK|qKPJkHs&Ff$u8wiGaUlsK=kg@K`=g^{tTrKx4qpmArJfdCsjIP{np z*;ut3Ss0X&Wc<&< z0*rVz1AdTlVGy6yfEh?3$2)TdgFz~jA;Wv-=&Ci_E4YfbcijKK)lGzx>(_#3wH(K! z+V@JVDBi1fDuHM2xta6N$M73G+$UoAipfCa%VZnR<;GPL+}t-O*9y<}+nz6B-Vprn euEGP;S!NfHGwytL=kDkKvN~2`;WsB)UIPHw3dtk@ literal 0 HcmV?d00001 diff --git a/assets/roots/eb8ebc9d472b7e0291771923f2a5f1c1c51e57b5-0.der b/assets/roots/eb8ebc9d472b7e0291771923f2a5f1c1c51e57b5-0.der new file mode 100644 index 0000000000000000000000000000000000000000..6dda6a38dc3d4d361acd02e4b52d4759d26ca563 GIT binary patch literal 579 zcmXqLVzM`AVm!KlnTe5!Nq{vpY|p2-C8nl_m+)U*?OJ5O#m1r4=5fxJg_+qP(U9AK zlZ`o)g-w_#G}utoKp4c~66SHqOwV*qEh~a|;WUmzV3M0HuD260TGTzWX6 z%s_yR9qeZ&MmARMMivGo<|GD|yUTCx?%Kiqo4a1|(uaNfia#62O6@Uub??~hJr|~a z`*7w0_pZd~K)1Siy7S-lCG&{CVK4Z4zD3WWdCm)a$6V{RmzRh{npMnYc77)yoKtjf zUB~i;*;}2@rQcEh&n&a}k=^gdpVegNSN3)(r*KAf72LSq%!wCQY literal 0 HcmV?d00001 diff --git a/assets/roots/ef58da4b2284f3860c68b18b916a064a520a9728-0.der b/assets/roots/ef58da4b2284f3860c68b18b916a064a520a9728-0.der new file mode 100644 index 0000000000000000000000000000000000000000..9c1e26c7ea1f4537df933c820e88e14304e8ffea GIT binary patch literal 551 zcmXqLVp29}VqCd^nTe5!NkmNa_~9F#%lBpSbalM_Q1J8acDr^1E;bIWHji_*EX>RX zfri`$oNUaYENsF|UIB*628tjKm#~y`Wm0NUNl|HWiGo*RL1LbQbH1LAf=@|`o}sLP zG)NJ%ut;!fa%oX&aAta*LQsBwiGs7Ep{aqKIIod`fr){IfrYV=sacdbuZbxVmljr* z83?ekgZ;$B$i}MO$ikq+oW#Jw9Z=GnVl;E+=1U12JCyp|N^~j?XuIg<#><{u=N+Wb zY`a4wH@H1vUVKjLZ8njq4~}d6pBw*V)yK`9$)u%e`!~k?5KVD3vz5QXD}F~G8r=5)a$H^^IIawzBlp^`@Pjh z7QtF>E^Oje=K^0o_@AcB>G#0@3+u(!Z>9-%z7}nEt7S59nc*hY)GC*l+3C&howjw} ls$C1@9B(p~Smn7|bv8&=8_ft!Ui~^bZ*uNxY0d;gKLA4QvNQky literal 0 HcmV?d00001 diff --git a/assets/roots/f026813c6ea2e5bbca921b4b884a50f107f0cbff-0.der b/assets/roots/f026813c6ea2e5bbca921b4b884a50f107f0cbff-0.der new file mode 100644 index 0000000000000000000000000000000000000000..507b8428b59681a113d25ccc2e355636521b74a9 GIT binary patch literal 1354 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 NE73A;ovl~TUI3Z=my-Yh literal 0 HcmV?d00001 diff --git a/assets/roots/f20ef3e3718f19d130d9cc7a2dc303161cb29cd0-0.der b/assets/roots/f20ef3e3718f19d130d9cc7a2dc303161cb29cd0-0.der new file mode 100644 index 0000000000000000000000000000000000000000..a42b51999e61426079da0e4a7399e6d3888d586b GIT binary patch literal 891 zcmXqLVlFpmVv1kD%*4pV#K>U4%f_kI=F#?@mywa1mBAp^klTQhjX9KsO_<3mz);md z8N}fdmI-!s_V-l?DJm^4Q3$RqE=kQTR&e&$)A0#$(KA#xPz5RD7M2T6O)f3UEU8p* z&d<#)&C5(qEXmBzQwYk>FL8D>GLRGJHLx@=H3DJ-17pi5ab6G;&ZV{WO^iy&9%f`^ zU~XdMXE11D9*eMzQw*#-0Hac?9RHf`TKs_nVBrGKKRX0(6wZ;ZFs}cpPe&LJzbr3W$P`+ z2?7VNU32O@wEv5R&{Urg*6(IdT$`&!Qm@_N@x6Gjj%T&?5z&2CVauZO%QAyjR_#3T zI%`&J&;?ztY*R5#ftCwdr>xi*%-x^Vh5OrgzfRxLvFhl);LuGIkJRqm&??DRp?5n` z>R0PmuZatvi(TzoWO;wejskfN`|dvuammd#&0113v(Mj)%(DKIFLqu2ub04Uza}xO zHR?RUAJ<-0*^tc~a8TDq?5>N6x>V_JCT2zk#>Gws4hFKo_>|>i5n~bIYF$~L$~vRQ zq45X*bRCP2TyJd78}Nao`577iv#yLvlfQh+Lgn4xROX)+ z|JSqZ+@VXi&-DH&{KKqyFelbHK4n{~b!WarS;f~oi#+5d?fWLb{`gD$-C4iKEJC5z zo<6zz+m5y7k!;+t<$I?4XIOoGsmJBKZGCu5Qbx`8zl*zC7OY9yr_I@J@*#_H_iW8D zn|}=E=aq9l`mNel;wF?c@tw)lH?t-_ciA_=u;{8M(>JE4U*uoc+?*D=^8cHzpEKS6 zn?^CMVmaLv-Cn3Uaqp5XRlIv=eGOM;U{@5k_p-dm>dC!)n|b5X*q&9!ag7=^%WKtN JGpy@12LJ#sRXYFx literal 0 HcmV?d00001 diff --git a/assets/roots/f33f2ef9470df6a3ca848f278ea48015299c54f1-0.der b/assets/roots/f33f2ef9470df6a3ca848f278ea48015299c54f1-0.der new file mode 100644 index 0000000000000000000000000000000000000000..767b3c2bb5faeae00886c1545445d7098c82c027 GIT binary patch literal 541 zcmXqLVv;myVw}H#nTe5!Nr3agCCulNnV#vKT2!K=;F*`KXQ*bN0+L`BmW4?ug!lw2xH>y4 z1Q=VGC$_8mg5Dmo{_*Blb#{cp zo0;oQ{>j)}y7M;Yn-juc{f=?TrWyF`mUri}NfdC>tXayz_dx08Wz&pvvyiAJdflJD`I8sQd^0^q&9C{`-;=+!h4*Z) z>e-TdDKj%!IN4e)K#y@@CELWg9{UgNV={0*ELy#B!Ni%KlULZ(yl`Xd*7;<}FlBDi diO#)}Eg~KxbXYs(aO=8r5k2)i687fa(l&tGlshk zeL5Taa{CY2^;t)zwqJKYIrT;pOMq=7$Nwj`n>SuNy6JBR=j}NyZ&bxa+UpG}W@km5 z+*Yh%WAc6G_xj7re#p4itkvn6Vt*rDAh=HHte%!(O*w~*fv^^oWg8Nig%v-r>e9g-|ruJGdRy?vU;uyoGmMzLlrnQ;w zxH0d>8WYbM$y1M>&h>DA^hr-b?t%!-NJLgmTi>-`~*Ze!x zq~yP^FUdXa>Dl&2FRt=S%zw&K5Dlh)k==mMqcEK=Fj~%uT)0j zn3Pq`4PKTrXSchiR&DIxv986#ZgZ9I3g$KMf$GbEgiH(a{2LCV?m-O^ht-D1|Y zH99hSm8iX$&$j;%t9eRkg=WUzqhDYDbN~6Ib;HAI!@0}qix%%ZqN8_aW;mZBk1OMp z)Yqw;$C8z}jXfuM*!($rZi8LlyzU?WJdc_dxic{{GB7S~>@#TWHsA-QTUlX7#{Vp= z2FyUpKo%sx$0Eievg*%70qORM@nSpo`+HA}W02Ws)L_5|lI91gX8~p)HUm!(U!KLu zz`?+7fz1M|Hj9jsk^(Dz{p6w?y%gONP#)3CODzHM^`ZRu)Ux;@VCINVPK-B-2W1XD zpe%Bd1?D_pl4WF=e0r(;Ou;v^c;j_MCm*xlC(v8r^dWD8yL;c_Nk+W|{okT{_jR6g zXUvjTQ0R<4(Ik-b;Q7o|*~hdNB%4mmYE@k;Zn1QBUBIWZSFNmC>WyHd1i~BKV8f&c_iyl zamf1`=b+?+f~O9-Zv0{${55l(&Euu_*1QPWzG>p$H7n&mwLcL*=DDKg2AAWPPj=7C zO<02NzG(d9s~_SzSM;I(nWy(m!Uaw$7l{}21{BDL<(%31-zv&Kx$1CUD!XjU!v8CI zk~?!ZIdoWFmoLxQ`e)A7Yw4NMD@tzv>HH>b^nHoSva;>&!S8!L`&Vvjj#@fxf@|W} zd(8f7{42UYFi+dCM)X2qR95wrX9bgQeaw8YDam(U|NqXpvt!S?zMR6O)ce4+{>4X@ z%EFwG<{!p8S&gJ4LIwRVJXLoueDn5J`n9BG-oE^qi%#x&qvSp1c*Yd*x<`E)cH54< z`f8Fh-@Jp};&xl%ud`DJcsOG+RIDyOp2{o zUY0Jod(*4>mj#?*VT(8LaO;}I9esJ^YV+wf@##7_!ujn{>phpr-ac!=q8I`mfjl=ljE2sncSL@7$cJ(QI@j#57UmXbGZQB-Cl$M literal 0 HcmV?d00001 diff --git a/assets/roots/f5042d4a785f7dab303cd2dc815d90dee6995f09-0.der b/assets/roots/f5042d4a785f7dab303cd2dc815d90dee6995f09-0.der new file mode 100644 index 0000000000000000000000000000000000000000..6a336d43999064b42158a620d996834f093cd489 GIT binary patch literal 572 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 literal 0 HcmV?d00001 diff --git a/assets/roots/f9780538aed47baadb39e8710440f329a43a372a-0.der b/assets/roots/f9780538aed47baadb39e8710440f329a43a372a-0.der new file mode 100644 index 0000000000000000000000000000000000000000..3a08309fc78bcf86f31db470fd7786f74f5b32e9 GIT binary patch literal 621 zcmXqLV#+jVVtl`VnTe5!NkCC6jAz{~p4ASn`?VM8TD>yhV&l+i^EhYA!pv-tYshWD z$;KSY!Y0h*>|rQizz5=R3A2ZL2B&(bRvIc9D1bz`g(dwxgF{>u-11BFQW8rt^Yav3 z^HTDQic?bzH4M~1YM6!PA!?v{72I?3lM-_jg7WiA6x^K^oE;71#CZ+P4NMIz4J=J8 zj7_4%c}O3XtzA$T%w_=^x z^5hAZEU#>AuZWkJ#vdSUYR;c^?9NSL-z9w$%%AOzHk`OP#2^S9(6aoDjQ?3!fbqm; zzz+;USz!>L)qojD8OVYJ_*lePL_FF>);vBuZ$g6A^;4biF0bv`5|9RTIj1%oBMW1b zxhNyZ1P0`IWzJwQNM$lmJDwSR=j!uK&rk65^c=oh*(05N^<>Z#Ki?*=V!2HXEh|+I z_(bJ;Y9>?&KdSS1ox)_uaN9(|h~xh(Po`}h_e9pm=FBYYm9p=?{$P<=n+$uCRax-g TLyO+Nuint$AHnY(Y`GTzFR{ZH literal 0 HcmV?d00001 diff --git a/assets/roots/fbb2191bc63ac8ff27f29ee542bf779ce0c298a1-0.der b/assets/roots/fbb2191bc63ac8ff27f29ee542bf779ce0c298a1-0.der new file mode 100644 index 0000000000000000000000000000000000000000..3cd7112df074647663dd393ac9b94632e4411029 GIT binary patch literal 1463 zcmXqLV%==e#5{WeGZP~dlYru!5ZxEw-V~Pq@_TW(Abh$3FB_*;n@8JsUPeY9RtAG) zLv903Hs(+kHen_gS3`9JRS<`ZN6sa+w4^vWBUK?JH77MYKUX0*HMz7Xv!qhNJvYh2 zP}@Kgq>!0M2}3@_Cs-jU*ij)UKfgr5$iT?hKu(+&NE#bi7#bOxnV3e2^O{%yx#mX3 zrk19bQS@;{6O$6M=NVZUn46gR84Q}3xR{!l7#ZH0E^KW0eA+8B`q1yd=5=a+ALe~- z6sj$B_}^aYq^bC}F-YgW&_T(T&Vzf8YFxeQo#S%N=QjH^uh6PfW=$++E0+FI|K|De zYSFxjKb5Z)RPX*e`{0|&=OtgI)^!vtDE{;9|JJaihs9YYGwhmnteG18g>iv8d(mQs z-~~@Mt!}mmH&&f8f1ii!jE9SN&5Jq9QW#iUTCw_b40HugYjAG-MNZZ>-vhe z9`^eEvi9l5*=_G0J*eIC-RQ4U%g5G}=9MqE+V78fQnIOEFp1%=MB2ie)ynZ7`>v+H z2)Pw)m2H;)vvH%byK%Y0&!AOzuQZ=;t(ZA$^3LLPCpSUGSO2(HN=v0?ZthF+elJ6ese_Go-d){iv`^UQUd&-|~XMJOR@Xfxq&24)Q ze82PjRrI<|XSSNQd0%*BzwO2kK8AO(kH7au{6F7q{xUf}?>lFq*&(6W@J}bUoc;Wc z>Av8dtexR4vXQ<$=T7h`AKI=qOF*RX-{ehQI~qRl3Cz2^B7n!dN&nS@uDK6&EdOua zQq1t=yT`KPHTyT+D`#onviR(5Qx)@H6DA8cIvty#R%)K+D6nSt*C~$=8EUV6t{is8 JRCUtk001V%kGTK< literal 0 HcmV?d00001 diff --git a/assets/roots/fcc3e143973af986f6b33251f56e17bbee86f58c-0.der b/assets/roots/fcc3e143973af986f6b33251f56e17bbee86f58c-0.der new file mode 100644 index 0000000000000000000000000000000000000000..bcafb43bea2ff5306b4e037e85359aaf537b0e5f GIT binary patch literal 1446 zcmXqLVqIj=#N4%jnTe5!NrZ8Vf!reQyI+qQYi)FPDg1FQz?av6myJ`a&7QWMBYN$IPP%QRkkMpOl!R5R{)^qTrlbRFavNnVeXXnV+ZNSXz>iUzAx=X&@)g zYiMp@VQ67sVQOJw6eZ4U4CWdcn_8M$M$ywLO-xG20l~=1z}&>d&tTBR#KqLa#K^Gi z%E!quTOB&NR`X7X4oF@%VM>X(Ytgmp)F@5w;_m5}2REvx3Du4XKV~(NNFT05wPn}%+X#P3H;KfX42B(9Vp58KwJ$&@p&v$~y zyHC2vwM>@q+A{yCP)gW+tyPY?8gg>PNrQpnf|Tki-gz13${_N+cKAH{#HKMaYJYhgU5z8yB1Z}Y7K^dsIL*>FKdI*aGg9+y=&&WglNsYu+8R zk3J7~;j+zQ_H&;7JTpJo^bzxd1rqyrf86)wT2DK#(|5L=A9LTbOj+@7+hw+64vF{Y z&ix!xb}rb8aoHrtH!O?$c^6&fJaz5#tdH-VV-Kue>UcgTxS-%<$1>e3*@?vgYHf*p z@zRI1AN5?_J@+89)2=DE;#Y1sb;g(Ly?Z9JzS8fH8t3-M2BfZ-kh{5L1IO_-D=xma zoAW1Ds##C_^w!TVbFG$(Cr(>bc)4JjsPA^4;&+lUN2Bc=|E(!au$E3f$8h%+$AqLU zPj%mUEYB978M3e4>+#As7N6Gwxgpoye|+&NN$hk(_FLPxU*!#!uJb-A{I-jc@#K!c zhy!U>8`HdxTs34$dRfxKbKBQVmx-B?fpM{ufrEi8FqzBpv52vVT$Gr+ zGcry#R^RkG!P|fjB+bvr_@9LZnCI9G_(6PO5TDh68Au_gGGKNCrZPqbqf-r==h}v_ z-Mw^4@52&CMecNuQ@MQ)PORcx)vFlZf6T7x)a}44L7T5kYgrnRQ2&&>PyXv2F44xP z*4wr#>2uBz7TcJ^W6F`cI=SfWoqcbPhd(n=D7{e6UUF|t*U}2om<4r z%4>0-X}5shP3udus}3w&_G7EzvoED5cLvWabytcK5j|cZy>0GGo*&X>A6r}QExaZ4 ze#s+&=~Wf?cmk)dP;}^S+speQ&d=Lyx3z+Nms8k7ZmIp%7nGcJTzY;#)!LFHBeQGD z1K~QwXR{8SKI)XRMQ%zphw6#KGwrV7t(WI(CMh)sPG^vqUY<2$(#e{HoclSoUQY^q z4$a#k`~8B+asD&Em;Yzpx3*xyE$+J~a;L}7s^>Iv{CjrMnW$W!PpdrtrTut$!}K+u zhTJ<3eX9$VoEA09l$s__y}-P0AJYQXy9!E=^JP0s5{^CN+R0hU{ek^O<}}mezm*&N zEG&1pYCBgfx~L=apI4)(XzJ`Z&fA?XGgdA1++7&-Y5%wX>I==*@7Pw}ao1`|?rf6@ zcSK?h_HTbJ{-k|-;f00Q%SEp^UH|miaAxf6yJETauh^7-xGhq8JNet)S + android:text="Usa o enderezo https:// normal do servidor, na casa ou fóra: MiniABS trae o seu propio TLS moderno. Na casa tamén vale o enderezo local http://. Se o certificado non se recoñece, amosaráseche a súa pegada dixital para que a comprobes." /> diff --git a/src/com/miniabs/Api.java b/src/com/miniabs/Api.java index 643f8b9..c1e9eca 100644 --- a/src/com/miniabs/Api.java +++ b/src/com/miniabs/Api.java @@ -26,7 +26,7 @@ import android.os.Build; /** Audiobookshelf REST client (API as of server 2.x, including the 2.26+ token system). */ final class Api { - static final String VERSION = "1.6"; + static final String VERSION = "1.7"; static class ApiException extends IOException { final int code; @@ -57,12 +57,12 @@ final class Api { // ------------------------------------------------------------------ plumbing static HttpURLConnection open(Context c, String url, String method) throws IOException { - HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + Trust.install(c); + HttpURLConnection conn = new NetConnection(new URL(url)); conn.setConnectTimeout(15000); conn.setReadTimeout(30000); conn.setUseCaches(false); conn.setInstanceFollowRedirects(true); - if (conn instanceof HttpsURLConnection) Trust.apply(c, (HttpsURLConnection) conn); setMethod(conn, method); conn.setRequestProperty("User-Agent", "MiniABS/" + VERSION + " (Android " + Build.VERSION.RELEASE + ")"); return conn; @@ -73,71 +73,31 @@ final class Api { } /** - * PATCH over a plain socket. Android 2.x's HttpURLConnection only sends a body with POST or PUT, - * and Audiobookshelf uses PATCH for "mark as played" and for saving progress without a session. + * PATCH: Audiobookshelf uses it for "mark as played" and for saving progress without a session. + * (Android 2.x's own HttpURLConnection can't send it; the bundled client can.) */ private static Object rawPatch(Context c, String path, JSONObject body, boolean mayRefresh) throws IOException { Prefs p = Prefs.get(c); if (mayRefresh) ensureFresh(c, 120); String used = p.token(); - URL u = new URL(p.server() + path); - boolean https = "https".equalsIgnoreCase(u.getProtocol()); - int port = u.getPort() > 0 ? u.getPort() : (https ? 443 : 80); - Socket sock = new Socket(); + HttpURLConnection conn = open(c, p.server() + path, "PATCH"); + conn.setInstanceFollowRedirects(false); + conn.setRequestProperty("Authorization", "Bearer " + used); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Accept", "application/json"); + conn.setDoOutput(true); int code; try { - sock.connect(new InetSocketAddress(u.getHost(), port), 15000); - sock.setSoTimeout(30000); - Socket io = sock; - if (https) { - SSLSocket ss = (SSLSocket) Trust.factory(c).createSocket(sock, u.getHost(), port, true); - try { // send the host name (SNI) where the phone supports it (Android 2.3+) - ss.getClass().getMethod("setHostname", String.class).invoke(ss, u.getHost()); - } catch (Exception ignored) { - } - ss.startHandshake(); - java.security.cert.Certificate[] peer = ss.getSession().getPeerCertificates(); - if (peer.length == 0 || !(peer[0] instanceof java.security.cert.X509Certificate) - || !Trust.hostMatches((java.security.cert.X509Certificate) peer[0], u.getHost())) { - ss.close(); - throw new SSLException("O certificado non corresponde a " + u.getHost()); - } - io = ss; + OutputStream os = conn.getOutputStream(); + os.write((body == null ? "{}" : body.toString()).getBytes("UTF-8")); + code = conn.getResponseCode(); + InputStream in = code >= 400 ? conn.getErrorStream() : conn.getInputStream(); + if (in != null) { + byte[] rest = new byte[1024]; + while (in.read(rest) > 0) { } // read the reply so the connection can be reused } - byte[] b = (body == null ? "{}" : body.toString()).getBytes("UTF-8"); - String host = u.getHost() + (u.getPort() > 0 ? ":" + u.getPort() : ""); - String head = "PATCH " + u.getFile() + " HTTP/1.1\r\n" - + "Host: " + host + "\r\n" - + "Authorization: Bearer " + used + "\r\n" - + "Content-Type: application/json\r\n" - + "Accept: application/json\r\n" - + "User-Agent: MiniABS/" + VERSION + "\r\n" - + "Content-Length: " + b.length + "\r\n" - + "Connection: close\r\n\r\n"; - OutputStream os = io.getOutputStream(); - os.write(head.getBytes("UTF-8")); - os.write(b); - os.flush(); - InputStream in = io.getInputStream(); - StringBuilder line = new StringBuilder(); - int ch; - while ((ch = in.read()) != -1 && ch != '\n' && line.length() < 200) line.append((char) ch); - String[] parts = line.toString().trim().split(" "); - if (parts.length < 2 || !parts[0].startsWith("HTTP/")) throw new IOException("Resposta inesperada do servidor"); - try { - code = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - throw new IOException("Resposta inesperada do servidor"); - } - byte[] rest = new byte[1024]; - int total = 0, n; - while (total < 16384 && (n = in.read(rest)) > 0) total += n; // read the reply politely - io.close(); } finally { - try { - sock.close(); - } catch (IOException ignored) { - } + conn.disconnect(); } if (code == 401) { if (mayRefresh && p.canRefresh()) { diff --git a/src/com/miniabs/Covers.java b/src/com/miniabs/Covers.java index af8c63c..f53f206 100644 --- a/src/com/miniabs/Covers.java +++ b/src/com/miniabs/Covers.java @@ -23,7 +23,7 @@ final class Covers { private static final int MAX_ENTRIES = 30; private static final Handler ui = new Handler(android.os.Looper.getMainLooper()); - private static final ExecutorService pool = Executors.newFixedThreadPool(2); + private static final ExecutorService pool = com.miniabs.net.Threads.pool("covers", 2); private static final Map cache = new LinkedHashMap(16, 0.75f, true) { @Override diff --git a/src/com/miniabs/DownloadService.java b/src/com/miniabs/DownloadService.java index 26783b0..96ad952 100644 --- a/src/com/miniabs/DownloadService.java +++ b/src/com/miniabs/DownloadService.java @@ -68,7 +68,22 @@ public class DownloadService extends IntentService { } @Override - protected void onHandleIntent(Intent intent) { + protected void onHandleIntent(final Intent intent) { + // IntentService's thread has Android 2.x's small default stack, which the TLS handshake + // overflows: download on a big-stack thread and wait for it + Thread t = com.miniabs.net.Threads.create(new Runnable() { + public void run() { + handle(intent); + } + }, "download"); + t.start(); + try { + t.join(); + } catch (InterruptedException ignored) { + } + } + + private void handle(Intent intent) { Episode e; try { e = Episode.fromStored(new JSONObject(intent.getStringExtra("ep"))); diff --git a/src/com/miniabs/Job.java b/src/com/miniabs/Job.java index 337eacd..fa18902 100644 --- a/src/com/miniabs/Job.java +++ b/src/com/miniabs/Job.java @@ -1,10 +1,17 @@ package com.miniabs; -import android.os.AsyncTask; +import java.util.concurrent.ExecutorService; -/** Background work with the result (or error) delivered on the UI thread. */ -abstract class Job extends AsyncTask { - private Exception error; +import android.os.Handler; +import android.os.Looper; + +/** + * Background work with the result (or error) delivered on the UI thread. Runs on big-stack + * threads, not AsyncTask's: the bundled TLS handshake overflows Android 2.x's default stack. + */ +abstract class Job { + private static final ExecutorService POOL = com.miniabs.net.Threads.pool("job", 3); + private static final Handler UI = new Handler(Looper.getMainLooper()); abstract T work() throws Exception; @@ -13,24 +20,26 @@ abstract class Job extends AsyncTask { void failed(Exception e) { } - @Override - protected final T doInBackground(Void... v) { - try { - return work(); - } catch (Exception e) { - error = e; - return null; - } - } - - @Override - protected final void onPostExecute(T result) { - if (error != null) failed(error); - else done(result); - } - Job start() { - execute(); + POOL.execute(new Runnable() { + public void run() { + T result = null; + Exception error = null; + try { + result = work(); + } catch (Exception e) { + error = e; + } + final T r = result; + final Exception err = error; + UI.post(new Runnable() { + public void run() { + if (err != null) failed(err); + else done(r); + } + }); + } + }); return this; } } diff --git a/src/com/miniabs/NetConnection.java b/src/com/miniabs/NetConnection.java new file mode 100644 index 0000000..683ef04 --- /dev/null +++ b/src/com/miniabs/NetConnection.java @@ -0,0 +1,177 @@ +package com.miniabs; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.miniabs.net.Http; + +/** + * An HttpURLConnection backed by the bundled TLS 1.2 client (com.miniabs.net, from Minamp), so + * the existing API, cover, download and stream code keeps its shape while the phone talks to the + * normal https:// address: Android 2.x's own TLS 1.0 can't. Any method works (PATCH too), and + * connections are kept alive between requests. + */ +final class NetConnection extends HttpURLConnection { + private final Map req = new LinkedHashMap(); + private ByteArrayOutputStream body; + private Http.Response resp; + private String verb = "GET"; + + NetConnection(URL u) { + super(u); + } + + @Override + public void setRequestMethod(String m) { + verb = m; // no validation: Android 2.x's refuses PATCH + method = m; + } + + @Override + public String getRequestMethod() { + return verb; + } + + @Override + public void setRequestProperty(String k, String v) { + req.put(k, v); + } + + @Override + public void addRequestProperty(String k, String v) { + String old = req.get(k); + req.put(k, old == null ? v : old + ", " + v); + } + + @Override + public String getRequestProperty(String k) { + return req.get(k); + } + + @Override + public OutputStream getOutputStream() throws IOException { + if (resp != null) throw new IOException("already connected"); + if (body == null) body = new ByteArrayOutputStream(); + return body; + } + + @Override + public void connect() throws IOException { + if (resp != null) return; + byte[] data = body == null ? null : body.toByteArray(); + if (data != null && "GET".equals(verb)) verb = "POST"; // as HttpURLConnection does with doOutput + int timeout = Math.max(getConnectTimeout(), getReadTimeout()); + if (timeout <= 0) timeout = 30000; + String u = url.toString(); + try { + resp = getInstanceFollowRedirects() ? Http.request(verb, u, req, data, timeout) + : Http.requestNoRedirect(verb, u, req, data, timeout); + } catch (com.miniabs.net.CertVerifier.Untrusted e) { + throw Trust.rejected(e); // the trust prompt offers this certificate + } catch (com.minamp.bc.tls.TlsException e) { + throw new javax.net.ssl.SSLException(e.getMessage()); + } + connected = true; + responseCode = resp.code; + } + + @Override + public int getResponseCode() throws IOException { + connect(); + return resp.code; + } + + @Override + public String getResponseMessage() throws IOException { + connect(); + String s = resp.status; + int a = s.indexOf(' '), b = a < 0 ? -1 : s.indexOf(' ', a + 1); + return b < 0 ? "" : s.substring(b + 1); + } + + @Override + public InputStream getInputStream() throws IOException { + connect(); + if (resp.code >= 400) throw new FileNotFoundException(url.toString()); + return resp.body; + } + + @Override + public InputStream getErrorStream() { + return resp != null && resp.code >= 400 ? resp.body : null; + } + + @Override + public String getHeaderField(String name) { + if (name == null) return resp == null ? null : resp.status; + try { + connect(); + } catch (IOException e) { + return null; + } + return resp.header(name); + } + + private List> entries() { + try { + connect(); + } catch (IOException e) { + return Collections.emptyList(); + } + return new ArrayList>(resp.headers.entrySet()); + } + + @Override + public String getHeaderFieldKey(int n) { + List> e = entries(); + return n >= 0 && n < e.size() ? e.get(n).getKey() : null; + } + + @Override + public String getHeaderField(int n) { + List> e = entries(); + return n >= 0 && n < e.size() ? e.get(n).getValue() : null; + } + + @Override + public Map> getHeaderFields() { + Map> m = new LinkedHashMap>(); + for (Map.Entry e : entries()) m.put(e.getKey(), Collections.singletonList(e.getValue())); + return m; + } + + @Override + public int getContentLength() { + try { + connect(); + } catch (IOException e) { + return -1; + } + return resp.contentLength > Integer.MAX_VALUE ? -1 : (int) resp.contentLength; + } + + @Override + public void disconnect() { + if (resp != null) resp.close(); + } + + /** Cuts the connection from another thread (a blocked read then fails). */ + void abort() { + Http.abort(resp); + } + + @Override + public boolean usingProxy() { + return false; + } +} diff --git a/src/com/miniabs/PlayerService.java b/src/com/miniabs/PlayerService.java index b5a408c..97b26d9 100644 --- a/src/com/miniabs/PlayerService.java +++ b/src/com/miniabs/PlayerService.java @@ -62,7 +62,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList } private final Handler h = new Handler(); - private final ExecutorService net = Executors.newSingleThreadExecutor(); + private final ExecutorService net = com.miniabs.net.Threads.pool("player-net", 1); private AudioManager am; private WifiManager.WifiLock wifi; diff --git a/src/com/miniabs/Roots.java b/src/com/miniabs/Roots.java deleted file mode 100644 index f59b07d..0000000 --- a/src/com/miniabs/Roots.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.miniabs; - -/** Generated by tools/gen_roots.py from the system CA store. Do not edit. */ -final class Roots { - private Roots() { - } - - static final String[] DER_BASE64 = { - // ISRG_Root_X1 sha256=96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6 - "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIElu" - + "dGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUw" - + "NjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNV" - + "BAMTDElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygch77ct984kIxuPOZX" - + "oHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDm" - + "gK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rg" - + "TBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3" - + "x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJ" - + "xQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8B" - + "CNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo" - + "//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD" - + "AQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1" - + "XIpu+ILlaS/V9lZLubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV0nxvwuo7" - + "7x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNz" - + "ktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3J" - + "LF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc" - + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7" - + "y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcY" - + "xn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=", - // GTS_Root_R1 sha256=d947432abde7b7fa90fc2e6b59101b1280e0e1c7e4e40fa3c6887fff57a7f4cf - "MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xl" - + "IFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBH" - + "MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi" - + "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo27xqLpeXo4xL+Sv2" - + "sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g" - + "4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy" - + "4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4" - + "Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Or" - + "p2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPv" - + "OmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1" - + "/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEa" - + "K0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmm" - + "WJyq+bXmYOfg6LEeQkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRiClmpIgUx" - + "PoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/K" - + "YnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflV" - + "y2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT" - + "6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNb" - + "oTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJw" - + "NQzcmRk13NfIRmPVNnGuV/u3gm3c", - // USERTrust_RSA_Certification_Authority sha256=e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2 - "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" - + "YvnKkKj9", - // DigiCert_Global_Root_G2 sha256=cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f - "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln" - + "aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0x" - + "MzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT" - + "EHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A" - + "MIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULec" - + "YxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hd" - + "G82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9" - + "g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB" - + "/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9" - + "PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/J" - + "sw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FB" - + "cf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyN" - + "xF1WW6LZZm6zNTflMrY=", - // Amazon_Root_CA_1 sha256=8ecde6884f3d87b1125ba31ac3fcb13d7016de7f57cc904fe1cb97c6ae98196e - "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMG" - + "QW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UE" - + "BhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC" - + "AQoCggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM9O6II8c+6zf1tRn4" - + "SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mj" - + "UcAwhmahRWa6VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7" - + "LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E" - + "BAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYo" - + "u6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gD" - + "S63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1" - + "bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZs" - + "G4q5WTP468SQvvG5", - // Microsoft_RSA_Root_Certificate_Authority_2017 sha256=c741f70f4b2a8d88bf2e71c14122ef53ef10eba0cfa5e64cfa20f418853073e0 - "MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWlj" - + "cm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw" - + "HhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0" - + "aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIiMA0GCSqGSIb3DQEB" - + "AQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/" - + "xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk" - + "3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XF" - + "WvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fz" - + "nhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IY" - + "ZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV" - + "/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/" - + "Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tN" - + "siMwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og6Lq46Brs" - + "TaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupK" - + "UY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2" - + "qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3" - + "pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB" - + "8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy" - + "4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D" - + "5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E", - // SSL.com_Root_Certification_Authority_RSA sha256=85666a562ee0be5ce925c1d8890a6f76a87ec16d4d7d5f29ea7419cf20123b69 - "MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYD" - + "VQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9u" - + "IEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4" - + "YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENlcnRp" - + "ZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh" - + "4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrz" - + "Qdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+l" - + "SgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9A" - + "hnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyA" - + "KoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/Zd" - + "Fr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRn" - + "hE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV" - + "7jiAJQ2mWTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQEAwIBhjAN" - + "BgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflV" - + "ma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAj" - + "RfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0" - + "cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jb" - + "utIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJ" - + "BjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y" - + "wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShYIc2wBlX7Jz9TkHCp" - + "BB5XJ7k=", - // Buypass_Class_2_Root_CA sha256=9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48 - "MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMz" - + "MjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkG" - + "A1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCC" - + "AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr6hxhFUi7HQfKjK6w" - + "3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fU" - + "l8FVM5I+GC911K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFO" - + "Bz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu" - + "8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPs" - + "aRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/Ti" - + "zPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGje" - + "yNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605" - + "MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6um" - + "wHJdELFx7rxP462sA20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EIosHsHdb9" - + "T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKn" - + "ToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6A" - + "DJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ" - + "I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0" - + "mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv" - + "4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=", - // HARICA_TLS_RSA_Root_CA_2021 sha256=d95d0e8eda79525bf9beb11b14d2100d3294985f0c62d9fabd9cd999eccb7b1d - "MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVs" - + "bGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBD" - + "QSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFj" - + "YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJvb3QgQ0EgMjAyMTCC" - + "AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FH" - + "ws+RFjZiPTgE4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig" - + "1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yyk" - + "gmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDak" - + "O/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMU" - + "uTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLu" - + "bpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N" - + "0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLu" - + "MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQUEapobQk1" - + "OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar" - + "5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYs" - + "TewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt" - + "jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzP" - + "rjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXP" - + "MThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn" - + "kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=", - // Certum_Trusted_Network_CA sha256=5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e - "MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xv" - + "Z2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVk" - + "IE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0" - + "byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIwIAYDVQQDExlDZXJ0" - + "dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7pt" - + "J11/91sts1rHUV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jU" - + "I4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et" - + "0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5" - + "p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8B" - + "Af8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7e" - + "DDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3" - + "zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssD" - + "xLQqKi2WF+A5VLxI03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=", - // Go_Daddy_Root_Certificate_Authority_-_G2 sha256=45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda - "MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcT" - + "ClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRl" - + "IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdB" - + "cml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UEAxMoR28gRGFkZHkg" - + "Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJ" - + "GKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT" - + "5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlR" - + "JQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdY" - + "q7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqa" - + "hQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7P" - + "dIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD0" - + "2JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPK" - + "f1855eJ1usV2GDPOLPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1", - // Starfield_Root_Certificate_Authority_-_G2 sha256=2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5 - "MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcT" - + "ClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290" - + "IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVT" - + "MRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJ" - + "bmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQAD" - + "ggEPADCCAQoCggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdh" - + "l+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNM" - + "Yq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP" - + "4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV" - + "HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+C" - + "hTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu34jd/IroAow57" - + "UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6" - + "nH7PfrHxBy22/L/KpL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZc2T5NnRe" - + "JaH1ZgUufzkVqSr7UIuOhWn0", - }; -} diff --git a/src/com/miniabs/StreamProxy.java b/src/com/miniabs/StreamProxy.java index 41f9bfe..7d4decf 100644 --- a/src/com/miniabs/StreamProxy.java +++ b/src/com/miniabs/StreamProxy.java @@ -37,7 +37,7 @@ final class StreamProxy implements Runnable { private StreamProxy(Context c) throws IOException { this.c = c.getApplicationContext(); server = new ServerSocket(0, 8, InetAddress.getByName("127.0.0.1")); - Thread t = new Thread(this, "MiniABS proxy"); + Thread t = com.miniabs.net.Threads.create(this, "MiniABS proxy"); t.setDaemon(true); t.start(); } @@ -85,7 +85,8 @@ final class StreamProxy implements Runnable { while (running) { try { final Socket s = server.accept(); - Thread t = new Thread(new Runnable() { + // big stack: the upstream TLS handshake overflows Android 2.x's default one + Thread t = com.miniabs.net.Threads.create(new Runnable() { public void run() { serve(s); } diff --git a/src/com/miniabs/Trust.java b/src/com/miniabs/Trust.java index d70d352..8f93ad4 100644 --- a/src/com/miniabs/Trust.java +++ b/src/com/miniabs/Trust.java @@ -2,7 +2,6 @@ package com.miniabs; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.security.KeyStore; import java.security.MessageDigest; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -15,21 +14,18 @@ import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; +import javax.net.ssl.SSLException; import android.content.Context; import android.util.Base64; +import com.miniabs.net.AssetRoots; +import com.miniabs.net.CertVerifier; + /** - * Decides which HTTPS certificates MiniABS accepts: - * 1. anything the phone itself trusts; - * 2. certificates chaining to the modern roots in {@link Roots} (Android 2.3's list predates Let's Encrypt); - * 3. certificates the user chose to trust after checking the fingerprint ("pins"). + * Decides which HTTPS certificates MiniABS accepts, for the bundled TLS 1.2 client: + * 1. certificates chaining to Mozilla's roots (assets/roots; Android 2.3's own list predates Let's Encrypt); + * 2. certificates the user chose to trust after checking the fingerprint ("pins"). */ final class Trust { private Trust() { @@ -54,26 +50,49 @@ final class Trust { static volatile Rejected lastRejected; - private static SSLSocketFactory factory; - private static List bundled; + private static volatile boolean installed; // ------------------------------------------------------------------ setup - static synchronized SSLSocketFactory factory(Context c) throws IOException { - if (factory == null) { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, new TrustManager[]{new Manager(c.getApplicationContext())}, null); - factory = ctx.getSocketFactory(); - } catch (Exception e) { - throw new IOException("As conexións seguras non están dispoñibles: " + e.getMessage()); - } + /** Bundled Mozilla roots plus the certificates the user chose to trust, for the TLS 1.2 client. */ + static void install(Context c) { + if (installed) return; + synchronized (Trust.class) { + if (installed) return; + final Context app = c.getApplicationContext(); + final AssetRoots mozilla = new AssetRoots(app); + CertVerifier.msgMismatch = "O certificado do servidor é doutro enderezo (%1$s)."; + CertVerifier.msgDates = "O certificado do servidor caducou ou a data do teléfono é incorrecta."; + CertVerifier.setRootStore(new CertVerifier.RootStore() { + public List find(byte[] subjectDer) { + List out = mozilla.find(subjectDer); + for (X509Certificate x : pinned(app)) { + if (!Arrays.equals(x.getSubjectX500Principal().getEncoded(), subjectDer)) continue; + if (out == null) out = new ArrayList(); + out.add(encoded(x)); + } + return out; + } + }); + installed = true; } - return factory; } - static void apply(Context c, HttpsURLConnection conn) throws IOException { - conn.setSSLSocketFactory(factory(c)); + /** The pins changed: rebuild the store (which also drops cached chains and TLS sessions). */ + private static void reinstall(Context c) { + installed = false; + install(c); + } + + /** Records an untrusted chain for the trust prompt and returns the error to throw. */ + static IOException rejected(CertVerifier.Untrusted u) { + try { + X509Certificate[] chain = new X509Certificate[u.chain.size()]; + for (int i = 0; i < chain.length; i++) chain[i] = parse(u.chain.get(i)); + lastRejected = new Rejected(chain, "untrusted"); + } catch (CertificateException ignored) { + } + return new SSLException("Certificate not trusted"); } /** Recently rejected (this attempt), or null. */ @@ -82,21 +101,6 @@ final class Trust { return r != null && System.currentTimeMillis() - r.at < 60000 ? r : null; } - private static synchronized List bundled() { - if (bundled == null) { - List list = new ArrayList(); - for (String b64 : Roots.DER_BASE64) { - try { - list.add(parse(Base64.decode(b64, Base64.DEFAULT))); - } catch (Exception ignored) { - // a root this phone can't parse; skip it - } - } - bundled = list; - } - return bundled; - } - static X509Certificate parse(byte[] der) throws CertificateException { return (X509Certificate) CertificateFactory.getInstance("X.509") .generateCertificate(new ByteArrayInputStream(der)); @@ -128,14 +132,14 @@ final class Trust { } } catch (Exception ignored) { } - factory = null; lastRejected = null; + reinstall(c); return top; } static synchronized void forgetPins(Context c) { Prefs.get(c).sp.edit().remove("pins").commit(); - factory = null; + reinstall(c); } /** Walks up the chain as far as the server's own certificates go. */ @@ -152,85 +156,6 @@ final class Trust { // ------------------------------------------------------------------ verification - private static final class Manager implements X509TrustManager { - private final Context c; - private X509TrustManager system; - - Manager(Context c) { - this.c = c; - try { - TrustManagerFactory f = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - f.init((KeyStore) null); - for (TrustManager t : f.getTrustManagers()) { - if (t instanceof X509TrustManager) system = (X509TrustManager) t; - } - } catch (Exception ignored) { - // no system trust store: rely on bundled roots and pins - } - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (chain == null || chain.length == 0) throw new CertificateException("No certificate"); - if (system != null) { - try { - system.checkServerTrusted(chain, authType); - return; - } catch (CertificateException ignored) { - // fall through to our own roots and pins - } catch (RuntimeException ignored) { - } - } - List anchors = new ArrayList(bundled()); - anchors.addAll(pinned(c)); - String why = verify(chain, anchors); - if (why == null) return; - lastRejected = new Rejected(chain.clone(), why); - throw new CertificateException("Certificate not trusted: " + why); - } - - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - throw new CertificateException("Not used"); - } - - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } - - /** - * Returns null if chain[0] chains up to one of the anchors, or a short reason why not. - * Every certificate that signs another must be a CA, so a website's own certificate - * can't be used to forge one for a different site. - */ - static String verify(X509Certificate[] chain, List anchors) { - X509Certificate cur = chain[0]; - try { - cur.checkValidity(); - } catch (Exception e) { - return "expired or not yet valid (check the phone's date)"; - } - for (int step = 0; step < 8; step++) { - for (X509Certificate a : anchors) { - if (Arrays.equals(encoded(a), encoded(cur))) return null; // trusted as-is - } - for (X509Certificate a : anchors) { - if (a.getSubjectX500Principal().equals(cur.getIssuerX500Principal()) && isCa(a) && signedBy(cur, a)) { - return null; - } - } - X509Certificate next = issuerIn(cur, chain); - if (next == null) return "unknown issuer"; - if (!isCa(next)) return "signed by a certificate that isn't a CA"; - try { - next.checkValidity(); - } catch (Exception e) { - return "expired intermediate certificate (check the phone's date)"; - } - cur = next; - } - return "chain too long"; - } - private static X509Certificate issuerIn(X509Certificate cert, X509Certificate[] chain) { for (X509Certificate x : chain) { if (x == cert || Arrays.equals(encoded(x), encoded(cert))) continue; diff --git a/src/com/miniabs/Util.java b/src/com/miniabs/Util.java index f8a27df..dc1032c 100644 --- a/src/com/miniabs/Util.java +++ b/src/com/miniabs/Util.java @@ -111,8 +111,8 @@ final class Util { return r.expired() ? "O certificado do servidor caducou ou a data do teléfono é incorrecta." : "Este teléfono non recoñece o certificado do servidor."; } - return "Non se puido establecer unha conexión segura. Android 2.3 só admite TLS 1.0, así que o servidor " - + "precisa un porto HTTPS antigo (ve o README). Na rede da casa tamén funciona o enderezo http://."; + String why = e.getMessage(); + return "Non se puido establecer unha conexión segura" + (why != null && why.length() > 0 ? " (" + why + ")." : "."); } if (e instanceof SocketException) return "Cortouse a conexión. Comproba a rede."; if (e instanceof FileNotFoundException) return "Non se atopou no servidor."; diff --git a/src/com/miniabs/net/AssetRoots.java b/src/com/miniabs/net/AssetRoots.java new file mode 100644 index 0000000..6ed9198 --- /dev/null +++ b/src/com/miniabs/net/AssetRoots.java @@ -0,0 +1,50 @@ +package com.miniabs.net; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import android.content.Context; + +/** Mozilla roots shipped as assets/roots/-.der */ +public final class AssetRoots implements CertVerifier.RootStore { + private final Context ctx; + private Set names; + + public AssetRoots(Context c) { ctx = c.getApplicationContext(); } + + public synchronized List find(byte[] subjectDer) { + try { + if (names == null) { + names = new HashSet(); + String[] l = ctx.getAssets().list("roots"); + if (l != null) for (int i = 0; i < l.length; i++) names.add(l[i]); + } + String h = CertVerifier.sha1Hex(subjectDer); + List out = null; + for (int i = 0; names.contains(h + "-" + i + ".der"); i++) { + if (out == null) out = new ArrayList(); + out.add(readAll(ctx.getAssets().open("roots/" + h + "-" + i + ".der"))); + } + return out; + } catch (IOException e) { + return null; + } + } + + static byte[] readAll(InputStream in) throws IOException { + try { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int n; + while ((n = in.read(buf)) > 0) b.write(buf, 0, n); + return b.toByteArray(); + } finally { + in.close(); + } + } +} diff --git a/src/com/miniabs/net/CertVerifier.java b/src/com/miniabs/net/CertVerifier.java new file mode 100644 index 0000000..96a6ce1 --- /dev/null +++ b/src/com/miniabs/net/CertVerifier.java @@ -0,0 +1,331 @@ +package com.miniabs.net; + +import java.io.IOException; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import com.minamp.bc.asn1.ASN1Encoding; +import com.minamp.bc.asn1.ASN1ObjectIdentifier; +import com.minamp.bc.asn1.ASN1OctetString; +import com.minamp.bc.asn1.pkcs.PKCSObjectIdentifiers; +import com.minamp.bc.asn1.x500.RDN; +import com.minamp.bc.asn1.x500.X500Name; +import com.minamp.bc.asn1.x500.style.BCStyle; +import com.minamp.bc.asn1.x500.style.IETFUtils; +import com.minamp.bc.asn1.x509.BasicConstraints; +import com.minamp.bc.asn1.x509.Certificate; +import com.minamp.bc.asn1.x509.Extension; +import com.minamp.bc.asn1.x509.Extensions; +import com.minamp.bc.asn1.x509.GeneralName; +import com.minamp.bc.asn1.x509.GeneralNames; +import com.minamp.bc.asn1.x9.X9ObjectIdentifiers; +import com.minamp.bc.crypto.Digest; +import com.minamp.bc.crypto.digests.SHA1Digest; +import com.minamp.bc.crypto.digests.SHA256Digest; +import com.minamp.bc.crypto.digests.SHA384Digest; +import com.minamp.bc.crypto.digests.SHA512Digest; +import com.minamp.bc.crypto.params.AsymmetricKeyParameter; +import com.minamp.bc.crypto.signers.DSADigestSigner; +import com.minamp.bc.crypto.signers.ECDSASigner; +import com.minamp.bc.crypto.signers.RSADigestSigner; +import com.minamp.bc.crypto.util.PublicKeyFactory; + +/** + * Minimal Web PKI path validation for the bundled TLS stack: + * hostname match, validity window, signature chain up to a bundled Mozilla root. + * Optionally one pinned leaf certificate (SHA-256) is trusted as-is, for self-signed servers. + * Shared with Minamp (tools/tlskit in the iOS ports' history); keep the copies in sync. + */ +public final class CertVerifier { + + /** Texts shown to the user; apps replace them with their own language. Formats take the host. */ + public static String msgMismatch = "The server certificate is for a different address (%1$s)."; + public static String msgDates = "The server certificate has expired, or the phone's date is wrong."; + + /** Thrown when the certificate is neither CA-trusted nor the pinned one: the app can offer to trust it. */ + public static final class Untrusted extends IOException { + public final String fingerprint, subject, issuer, expires; + public final boolean changed; // a pin was set and this is a different certificate + public final List chain; // as the server sent it, leaf first + + Untrusted(String fp, String subject, String issuer, String expires, boolean changed, List chain) { + super("untrusted certificate " + fp); + this.chain = chain; + this.fingerprint = fp; + this.subject = subject; + this.issuer = issuer; + this.expires = expires; + this.changed = changed; + } + } + + private static volatile String pin = ""; + + /** SHA-256 of the one leaf certificate to accept without a CA (hex, separators ignored); "" for none. */ + public static void setPin(String p) { + String n = norm(p); + if (!n.equals(pin)) { + pin = n; + Http.forgetSessions(); + } + } + + public static String norm(String s) { + if (s == null) return ""; + return s.replaceAll("[^0-9A-Fa-f]", "").toUpperCase(Locale.US); + } + + /** "AB:CD:..." SHA-256 of a DER certificate. */ + public static String sha256(byte[] der) { + try { + byte[] d = MessageDigest.getInstance("SHA-256").digest(der); + StringBuilder b = new StringBuilder(); + for (int i = 0; i < d.length; i++) { + if (i > 0) b.append(':'); + String x = Integer.toHexString(d[i] & 0xff).toUpperCase(Locale.US); + if (x.length() == 1) b.append('0'); + b.append(x); + } + return b.toString(); + } catch (Exception e) { + return ""; + } + } + + private static Untrusted untrusted(List chainDer, boolean changed) { + byte[] leafDer = chainDer.get(0); + String subject = "?", issuer = "?", expires = "?"; + try { + Certificate c = Certificate.getInstance(leafDer); + subject = String.valueOf(c.getSubject()); + issuer = String.valueOf(c.getIssuer()); + expires = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.US).format(c.getEndDate().getDate()); + } catch (Exception ignored) {} + return new Untrusted(sha256(leafDer), subject, issuer, expires, changed, chainDer); + } + + /** Lookup of trusted root certificates (DER) by the DER encoding of their subject name. */ + public interface RootStore { + List find(byte[] subjectDer); + } + + private static RootStore roots; + /** Chains already proven to lead to a trusted root (keyed by a hash of the chain), so a + * reconnect only re-checks the host name and dates, not every signature again. */ + private static final java.util.Set verifiedChains = new java.util.HashSet(); + + /** Changing what's trusted invalidates everything trusted so far: cached chains and TLS sessions. */ + public static void setRootStore(RootStore store) { + roots = store; + synchronized (verifiedChains) { verifiedChains.clear(); } + Http.forgetSessions(); + } + + private CertVerifier() {} + + + public static void verify(List chainDer, String host) throws IOException { + if (chainDer == null || chainDer.isEmpty()) throw new IOException("Server sent no certificate"); + String p = pin; + if (p.length() > 0) { + // a pinned server: exactly that certificate, whatever its name or issuer + if (norm(sha256(chainDer.get(0))).equals(p)) return; + throw untrusted(chainDer, true); + } + if (roots == null) throw new IOException("No trust store configured"); + java.io.ByteArrayOutputStream all = new java.io.ByteArrayOutputStream(); + for (int i = 0; i < chainDer.size(); i++) all.write(chainDer.get(i), 0, chainDer.get(i).length); + String chainKey = sha1Hex(all.toByteArray()); + boolean known; + synchronized (verifiedChains) { known = verifiedChains.contains(chainKey); } + if (known) { + Certificate leaf = Certificate.getInstance(chainDer.get(0)); + if (!hostMatches(leaf, host)) throw new IOException(String.format(msgMismatch, host)); + if (new Date().getTime() > 1704067200000L) checkDates(leaf, new Date()); + return; + } + verifyFully(chainDer, host); + synchronized (verifiedChains) { verifiedChains.add(chainKey); } + } + + private static void verifyFully(List chainDer, String host) throws IOException { + final List chain0 = chainDer; + List chain = new ArrayList(); + for (int i = 0; i < chainDer.size(); i++) chain.add(Certificate.getInstance(chainDer.get(i))); + + Certificate leaf = chain.get(0); + if (!hostMatches(leaf, host)) { + throw new IOException(String.format(msgMismatch, host)); + } + + Date now = new Date(); + // An unset clock on an old phone (e.g. 1980 after a battery pull) would make every + // certificate look "not yet valid"; only enforce dates when the clock looks sane. + boolean clockSane = now.getTime() > 1704067200000L; // 2024-01-01 + + Certificate current = leaf; + for (int depth = 0; depth < 8; depth++) { + if (clockSane) checkDates(current, now); + + byte[] issuerDer = current.getIssuer().getEncoded(ASN1Encoding.DER); + + // 1. Anchored directly by a trusted root? + List anchors = roots.find(issuerDer); + if (anchors != null) { + byte[] currentDer = current.getEncoded(ASN1Encoding.DER); + for (int i = 0; i < anchors.size(); i++) { + // the very certificate the user chose to trust (e.g. self-signed) + if (java.util.Arrays.equals(anchors.get(i), currentDer)) return; + Certificate root = Certificate.getInstance(anchors.get(i)); + // anything that signs must be a CA: a trusted site's own certificate can't vouch for another + if (isCa(root) && signatureValid(current, root)) return; // trusted + } + } + + // 2. Otherwise find the issuing intermediate among what the server sent. + Certificate next = null; + for (int i = 1; i < chain.size(); i++) { + Certificate cand = chain.get(i); + if (cand == current) continue; + if (!java.util.Arrays.equals(cand.getSubject().getEncoded(ASN1Encoding.DER), issuerDer)) continue; + if (!isCa(cand)) continue; + if (signatureValid(current, cand)) { next = cand; break; } + } + if (next == null) throw untrusted(chain0, false); + current = next; + } + throw new IOException("Certificate chain too long"); + } + + private static void checkDates(Certificate c, Date now) throws IOException { + Date from = c.getStartDate().getDate(); + Date to = c.getEndDate().getDate(); + if (now.before(from) || now.after(to)) { + throw new IOException(msgDates); + } + } + + private static boolean isCa(Certificate c) { + Extensions ext = c.getTBSCertificate().getExtensions(); + if (ext == null) return c.getTBSCertificate().getVersionNumber() < 3; // v1 roots/intermediates + BasicConstraints bc = BasicConstraints.fromExtensions(ext); + return bc != null && bc.isCA(); + } + + static boolean signatureValid(Certificate subject, Certificate issuer) { + try { + ASN1ObjectIdentifier alg = subject.getSignatureAlgorithm().getAlgorithm(); + byte[] tbs = subject.getTBSCertificate().getEncoded(ASN1Encoding.DER); + byte[] sig = subject.getSignature().getOctets(); + AsymmetricKeyParameter key = PublicKeyFactory.createKey(issuer.getSubjectPublicKeyInfo()); + + if (alg.equals(PKCSObjectIdentifiers.sha256WithRSAEncryption)) return rsa(new SHA256Digest(), key, tbs, sig); + if (alg.equals(PKCSObjectIdentifiers.sha384WithRSAEncryption)) return rsa(new SHA384Digest(), key, tbs, sig); + if (alg.equals(PKCSObjectIdentifiers.sha512WithRSAEncryption)) return rsa(new SHA512Digest(), key, tbs, sig); + if (alg.equals(PKCSObjectIdentifiers.sha1WithRSAEncryption)) return rsa(new SHA1Digest(), key, tbs, sig); + if (alg.equals(X9ObjectIdentifiers.ecdsa_with_SHA256)) return ecdsa(new SHA256Digest(), key, tbs, sig); + if (alg.equals(X9ObjectIdentifiers.ecdsa_with_SHA384)) return ecdsa(new SHA384Digest(), key, tbs, sig); + if (alg.equals(X9ObjectIdentifiers.ecdsa_with_SHA512)) return ecdsa(new SHA512Digest(), key, tbs, sig); + return false; + } catch (Exception e) { + return false; + } + } + + private static boolean rsa(Digest d, AsymmetricKeyParameter key, byte[] tbs, byte[] sig) { + RSADigestSigner s = new RSADigestSigner(d); + s.init(false, key); + s.update(tbs, 0, tbs.length); + return s.verifySignature(sig); + } + + private static boolean ecdsa(Digest d, AsymmetricKeyParameter key, byte[] tbs, byte[] sig) { + DSADigestSigner s = new DSADigestSigner(new ECDSASigner(), d); + s.init(false, key); + s.update(tbs, 0, tbs.length); + return s.verifySignature(sig); + } + + // ---- hostname checking ---- + + static boolean hostMatches(Certificate leaf, String host) { + host = host.toLowerCase(Locale.US); + boolean ip = isIpLiteral(host); + boolean sawDns = false; + Extensions ext = leaf.getTBSCertificate().getExtensions(); + if (ext != null) { + GeneralNames names = GeneralNames.fromExtensions(ext, Extension.subjectAlternativeName); + if (names != null) { + GeneralName[] arr = names.getNames(); + for (int i = 0; i < arr.length; i++) { + GeneralName n = arr[i]; + if (n.getTagNo() == GeneralName.dNSName) { + sawDns = true; + // String.valueOf, not getName().toString(): Dalvik < 4.0 can't call Object methods via an interface + if (!ip && dnsMatch(String.valueOf(n.getName()), host)) return true; + } else if (n.getTagNo() == GeneralName.iPAddress && ip) { + byte[] a = ASN1OctetString.getInstance(n.getName()).getOctets(); + if (ipString(a).equals(host)) return true; + } + } + } + } + if (!sawDns && !ip) { + X500Name subject = X500Name.getInstance(leaf.getSubject()); + RDN[] cns = subject.getRDNs(BCStyle.CN); + for (int i = 0; i < cns.length; i++) { + String cn = IETFUtils.valueToString(cns[i].getFirst().getValue()); + if (dnsMatch(cn, host)) return true; + } + } + return false; + } + + static boolean dnsMatch(String pattern, String host) { + pattern = pattern.toLowerCase(Locale.US); + if (pattern.endsWith(".")) pattern = pattern.substring(0, pattern.length() - 1); + if (host.endsWith(".")) host = host.substring(0, host.length() - 1); + if (pattern.startsWith("*.")) { + String suffix = pattern.substring(1); // ".example.com" + if (!host.endsWith(suffix)) return false; + String label = host.substring(0, host.length() - suffix.length()); + return label.length() > 0 && label.indexOf('.') < 0; + } + return pattern.equals(host); + } + + static boolean isIpLiteral(String host) { + if (host.indexOf(':') >= 0) return true; + for (int i = 0; i < host.length(); i++) { + char c = host.charAt(i); + if (!(c == '.' || (c >= '0' && c <= '9'))) return false; + } + return true; + } + + private static String ipString(byte[] a) { + if (a.length == 4) { + return (a[0] & 255) + "." + (a[1] & 255) + "." + (a[2] & 255) + "." + (a[3] & 255); + } + return ""; + } + + public static String sha1Hex(byte[] data) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] h = md.digest(data); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < h.length; i++) { + String s = Integer.toHexString(h[i] & 255); + if (s.length() == 1) sb.append('0'); + sb.append(s); + } + return sb.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/com/miniabs/net/DiagLog.java b/src/com/miniabs/net/DiagLog.java new file mode 100644 index 0000000..5d4c2e5 --- /dev/null +++ b/src/com/miniabs/net/DiagLog.java @@ -0,0 +1,47 @@ +package com.miniabs.net; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * In-memory diagnostics log (last few hundred lines): every request with its status and timing, + * sign-in steps, stream attempts. Viewable in the app and savable to the SD card, so a problem + * on a real server can be reported precisely. Tokens are never logged. + */ +public final class DiagLog { + private static final int MAX = 400; + private static final List lines = new ArrayList(); + private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss", Locale.US); + + private DiagLog() {} + + public static void log(String msg) { + String line; + synchronized (fmt) { line = fmt.format(new Date()) + " " + msg; } + synchronized (lines) { + lines.add(line); + if (lines.size() > MAX) lines.remove(0); + } + } + + public static String dump() { + StringBuilder sb = new StringBuilder(); + synchronized (lines) { + for (int i = 0; i < lines.size(); i++) sb.append(lines.get(i)).append('\n'); + } + return sb.toString(); + } + + public static void clear() { + synchronized (lines) { lines.clear(); } + } + + /** URL without the token value, safe to show and share. */ + public static String redact(String url) { + if (url == null) return ""; + return url.replaceAll("((?:X-Plex-Token|token|T|Passwd)=)[^&]*", "$1***"); + } +} diff --git a/src/com/miniabs/net/Http.java b/src/com/miniabs/net/Http.java new file mode 100644 index 0000000..5c285a2 --- /dev/null +++ b/src/com/miniabs/net/Http.java @@ -0,0 +1,588 @@ +package com.miniabs.net; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Vector; + +import com.minamp.bc.tls.DefaultTlsClient; +import com.minamp.bc.tls.NameType; +import com.minamp.bc.tls.ServerName; +import com.minamp.bc.tls.TlsAuthentication; +import com.minamp.bc.tls.TlsClientProtocol; +import com.minamp.bc.tls.TlsCredentials; +import com.minamp.bc.tls.TlsServerCertificate; +import com.minamp.bc.tls.TlsSession; +import com.minamp.bc.tls.CertificateRequest; +import com.minamp.bc.tls.crypto.TlsCrypto; +import com.minamp.bc.tls.crypto.impl.bc.BcTlsCrypto; + +/** + * Small HTTP/1.1 client with its own TLS 1.2 (Bouncy Castle), keep-alive pooling and + * session resumption. Android 2.x's built-in SSL only speaks TLS 1.0 and lacks today's roots, + * so every request in the app goes through here. From Minamp; keep the copies in sync. + */ +public final class Http { + + public static String USER_AGENT = "Minamp/1.0"; + + /** Optional HTTP CONNECT proxy, only used when running the networking code on a desktop JVM. */ + public static String proxyHost; + public static int proxyPort; + + private static TlsCrypto crypto; + private static final Map sessions = new HashMap(); + private static final Map> pool = new HashMap>(); + private static final long IDLE_MS = 25000; + + private Http() {} + + private static synchronized TlsCrypto crypto() { + if (crypto == null) crypto = new BcTlsCrypto(new SecureRandom()); + return crypto; + } + + // ------------------------------------------------------------------ URL + + public static final class Url { + public final boolean https; + public final String host; + public final int port; + public final String path; // includes query + + public Url(String s) throws IOException { + String lower = s.toLowerCase(Locale.US); + int start; + if (lower.startsWith("https://")) { https = true; start = 8; } + else if (lower.startsWith("http://")) { https = false; start = 7; } + else throw new IOException("Bad URL: " + s); + int slash = s.indexOf('/', start); + String hostPort = slash < 0 ? s.substring(start) : s.substring(start, slash); + path = slash < 0 ? "/" : s.substring(slash); + int colon = hostPort.lastIndexOf(':'); + if (colon > 0 && hostPort.indexOf(']') < colon) { + host = hostPort.substring(0, colon); + try { port = Integer.parseInt(hostPort.substring(colon + 1)); } + catch (NumberFormatException e) { throw new IOException("Bad port in " + s); } + } else { + host = hostPort; + port = https ? 443 : 80; + } + } + + String key() { return (https ? "s:" : "p:") + host + ":" + port; } + + String hostHeader() { + boolean dflt = (https && port == 443) || (!https && port == 80); + return dflt ? host : host + ":" + port; + } + } + + // ------------------------------------------------------------------ connection + + static final class Conn { + final String key; + Socket socket; + InputStream in; + OutputStream out; + TlsClientProtocol tls; + long idleSince; + + Conn(String key) { this.key = key; } + + void close() { + try { if (tls != null) tls.close(); } catch (Exception ignored) {} + try { if (socket != null) socket.close(); } catch (Exception ignored) {} + } + } + + private static Conn take(String key) { + synchronized (pool) { + List list = pool.get(key); + if (list == null) return null; + long now = System.currentTimeMillis(); + while (!list.isEmpty()) { + Conn c = list.remove(list.size() - 1); + if (now - c.idleSince < IDLE_MS && !c.socket.isClosed()) return c; + c.close(); + } + return null; + } + } + + static void release(Conn c) { + c.idleSince = System.currentTimeMillis(); + synchronized (pool) { + List list = pool.get(c.key); + if (list == null) { list = new ArrayList(); pool.put(c.key, list); } + if (list.size() >= 4) { list.remove(0).close(); } + list.add(c); + } + } + + /** Forget resumable TLS sessions (next connection to each host does a full, verified handshake). */ + static void forgetSessions() { + synchronized (sessions) { sessions.clear(); } + evictAll(); + } + + /** Drop all pooled connections (e.g. after the network changed). */ + public static void evictAll() { + final List doomed = new ArrayList(); + synchronized (pool) { + for (Iterator> it = pool.values().iterator(); it.hasNext();) doomed.addAll(it.next()); + pool.clear(); + } + if (doomed.isEmpty()) return; + // closing TLS sends an encrypted alert: keep that off the (small-stack) caller thread + Threads.start(new Runnable() { + public void run() { for (int i = 0; i < doomed.size(); i++) doomed.get(i).close(); } + }, "http-evict"); + } + + private static Conn connect(final Url u, int timeoutMs) throws IOException { + Conn c = new Conn(u.key()); + Socket s = new Socket(); + boolean ok = false; + try { + if (proxyHost != null) { + s.connect(new InetSocketAddress(proxyHost, proxyPort), timeoutMs); + s.setSoTimeout(timeoutMs); + OutputStream po = s.getOutputStream(); + String req = "CONNECT " + u.host + ":" + u.port + " HTTP/1.1\r\nHost: " + u.host + ":" + u.port + "\r\n\r\n"; + po.write(req.getBytes("ISO-8859-1")); + po.flush(); + InputStream pi = s.getInputStream(); + String status = readLine(pi); + if (status == null || status.indexOf(" 200") < 0) throw new IOException("Proxy refused: " + status); + String line; + while ((line = readLine(pi)) != null && line.length() > 0) { /* skip headers */ } + } else { + s.connect(new InetSocketAddress(u.host, u.port), timeoutMs); + s.setSoTimeout(timeoutMs); + } + s.setTcpNoDelay(true); + c.socket = s; + if (u.https) { + TlsClientProtocol proto = new TlsClientProtocol(s.getInputStream(), s.getOutputStream()); + proto.connect(new Client(crypto(), u.host, u.key())); + c.tls = proto; + c.in = new BufferedInputStream(proto.getInputStream(), 8192); + c.out = new BufferedOutputStream(proto.getOutputStream(), 4096); + } else { + c.in = new BufferedInputStream(s.getInputStream(), 8192); + c.out = new BufferedOutputStream(s.getOutputStream(), 4096); + } + ok = true; + return c; + } finally { + if (!ok) { try { s.close(); } catch (Exception ignored) {} } + } + } + + private static final class Client extends DefaultTlsClient { + private final String host; + private final String sessionKey; + + private final long started = System.currentTimeMillis(); + + Client(TlsCrypto crypto, String host, String sessionKey) { + super(crypto); + this.host = host; + this.sessionKey = sessionKey; + } + + @Override + protected Vector getSNIServerNames() { + if (CertVerifier.isIpLiteral(host)) return null; + Vector v = new Vector(); + try { + v.addElement(new ServerName(NameType.host_name, host.getBytes("US-ASCII"))); + } catch (java.io.UnsupportedEncodingException e) { + return null; + } + return v; + } + + /** + * TLS 1.2 only: this TLS stack can resume 1.2 sessions but not 1.3 ones, and on a 600 MHz + * phone a resumed handshake (no key exchange, no signature checks) is the difference + * between ~10 s and well under a second for every new connection. + */ + @Override + protected com.minamp.bc.tls.ProtocolVersion[] getSupportedVersions() { + return com.minamp.bc.tls.ProtocolVersion.TLSv12.only(); + } + + @Override + public TlsSession getSessionToResume() { + synchronized (sessions) { return sessions.get(sessionKey); } + } + + @Override + public void notifyHandshakeComplete() throws IOException { + super.notifyHandshakeComplete(); + boolean resumed = context.getSecurityParametersConnection().isResumedSession(); + DiagLog.log("TLS " + (resumed ? "resumed" : "full handshake") + " with " + host + + " in " + (System.currentTimeMillis() - started) + "ms"); + TlsSession s = context.getSession(); + if (s != null && s.isResumable()) { + synchronized (sessions) { sessions.put(sessionKey, s); } + } + } + + public TlsAuthentication getAuthentication() throws IOException { + return new TlsAuthentication() { + public void notifyServerCertificate(TlsServerCertificate sc) throws IOException { + com.minamp.bc.tls.Certificate cert = sc.getCertificate(); + List chain = new ArrayList(); + for (int i = 0; i < cert.getLength(); i++) chain.add(cert.getCertificateAt(i).getEncoded()); + CertVerifier.verify(chain, host); + } + + public TlsCredentials getClientCredentials(CertificateRequest req) { + return null; + } + }; + } + } + + // ------------------------------------------------------------------ request / response + + public static final class Response { + public final int code; + public final String status; + public final Map headers; // lower-case names + public final InputStream body; + public final long contentLength; // -1 if unknown + public final boolean chunked; + + Response(int code, String status, Map headers, InputStream body, long len, boolean chunked) { + this.code = code; this.status = status; this.headers = headers; this.body = body; + this.contentLength = len; this.chunked = chunked; + } + + public String header(String name) { return headers.get(name.toLowerCase(Locale.US)); } + + public void close() { + try { body.close(); } catch (IOException ignored) {} + } + + public byte[] readAll() throws IOException { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(contentLength > 0 && contentLength < 4000000 ? (int) contentLength : 8192); + byte[] buf = new byte[8192]; + int n; + while ((n = body.read(buf)) > 0) bos.write(buf, 0, n); + return bos.toByteArray(); + } finally { + close(); + } + } + + public String readString() throws IOException { + return new String(readAll(), "UTF-8"); + } + } + + public static Response get(String url, Map headers, int timeoutMs) throws IOException { + return request("GET", url, headers, null, timeoutMs); + } + + public static Response request(String method, String url, Map headers, byte[] body, int timeoutMs) throws IOException { + return request(method, url, headers, body, timeoutMs, true); + } + + /** A single request; 3xx responses are returned to the caller. */ + public static Response requestNoRedirect(String method, String url, Map headers, byte[] body, int timeoutMs) throws IOException { + return request(method, url, headers, body, timeoutMs, false); + } + + private static Response request(String method, String url, Map headers, byte[] body, int timeoutMs, boolean follow) throws IOException { + for (int redirects = 0; ; redirects++) { + long t0 = System.currentTimeMillis(); + Response r; + try { + r = requestOnce(method, url, headers, body, timeoutMs); + } catch (IOException e) { + DiagLog.log(method + " " + DiagLog.redact(url) + " -> FAILED after " + (System.currentTimeMillis() - t0) + "ms: " + Threads.describe(e)); + throw e; + } catch (RuntimeException e) { + DiagLog.log(method + " " + DiagLog.redact(url) + " -> FAILED: " + Threads.describe(e)); + throw e; + } + if (!url.contains("/photo/:/transcode") && !url.contains("/cover")) { // artwork would drown out everything else + DiagLog.log(method + " " + DiagLog.redact(url) + " -> " + r.code + " (" + (System.currentTimeMillis() - t0) + "ms)"); + } + if (follow && (r.code == 301 || r.code == 302 || r.code == 307 || r.code == 308) && redirects < 4 + && ("GET".equals(method) || "HEAD".equals(method))) { + String loc = r.header("location"); + r.close(); + if (loc == null) throw new IOException("Redirect without location"); + if (loc.startsWith("/")) { + Url u = new Url(url); + loc = (u.https ? "https://" : "http://") + u.hostHeader() + loc; + } + url = loc; + continue; + } + return r; + } + } + + private static Response requestOnce(String method, String url, Map headers, byte[] body, int timeoutMs) throws IOException { + Url u = new Url(url); + // A pooled connection may have been closed by the server; retry once on a fresh one. + for (int attempt = 0; attempt < 2; attempt++) { + Conn c = take(u.key()); + boolean pooled = c != null; + if (c == null) c = connect(u, timeoutMs); + try { + c.socket.setSoTimeout(timeoutMs); + writeRequest(c, u, method, headers, body); + return readResponse(c, method); + } catch (IOException e) { + c.close(); + if (!pooled) throw e; + } + } + throw new IOException("Request failed"); + } + + private static void writeRequest(Conn c, Url u, String method, Map headers, byte[] body) throws IOException { + StringBuilder sb = new StringBuilder(512); + sb.append(method).append(' ').append(u.path).append(" HTTP/1.1\r\n"); + sb.append("Host: ").append(u.hostHeader()).append("\r\n"); + boolean hasUa = false; + if (headers != null) { + for (Map.Entry e : headers.entrySet()) { + if (e.getValue() == null) continue; + if ("user-agent".equalsIgnoreCase(e.getKey())) hasUa = true; + sb.append(e.getKey()).append(": ").append(e.getValue()).append("\r\n"); + } + } + if (!hasUa) sb.append("User-Agent: ").append(USER_AGENT).append("\r\n"); + sb.append("Connection: keep-alive\r\n"); + if (body != null || "POST".equals(method) || "PUT".equals(method)) { + sb.append("Content-Length: ").append(body == null ? 0 : body.length).append("\r\n"); + } + sb.append("\r\n"); + c.out.write(sb.toString().getBytes("UTF-8")); + if (body != null) c.out.write(body); + c.out.flush(); + } + + private static Response readResponse(Conn c, String method) throws IOException { + String statusLine = readLine(c.in); + if (statusLine == null) throw new IOException("Connection closed"); + // HTTP/1.1 200 OK + int sp = statusLine.indexOf(' '); + if (sp < 0) throw new IOException("Bad status line"); + int sp2 = statusLine.indexOf(' ', sp + 1); + int code; + try { + code = Integer.parseInt(sp2 < 0 ? statusLine.substring(sp + 1).trim() : statusLine.substring(sp + 1, sp2)); + } catch (NumberFormatException e) { + throw new IOException("Bad status line"); + } + boolean http10 = statusLine.startsWith("HTTP/1.0"); + Map h = new LinkedHashMap(); + String line; + while ((line = readLine(c.in)) != null && line.length() > 0) { + int colon = line.indexOf(':'); + if (colon <= 0) continue; + String name = line.substring(0, colon).trim().toLowerCase(Locale.US); + String val = line.substring(colon + 1).trim(); + String prev = h.get(name); + h.put(name, prev == null ? val : prev + ", " + val); + } + String te = h.get("transfer-encoding"); + String cl = h.get("content-length"); + String conn = h.get("connection"); + boolean keepAlive = !http10 && (conn == null || conn.toLowerCase(Locale.US).indexOf("close") < 0); + boolean chunked = te != null && te.toLowerCase(Locale.US).indexOf("chunked") >= 0; + long len = -1; + if (!chunked && cl != null) { + try { len = Long.parseLong(cl.trim()); } catch (NumberFormatException ignored) {} + } + InputStream body; + boolean noBody = "HEAD".equals(method) || code == 204 || code == 304 || (code >= 100 && code < 200); + if (noBody) { + body = new FixedStream(c, 0, keepAlive); + len = "HEAD".equals(method) ? len : 0; + } else if (chunked) { + body = new ChunkedStream(c, keepAlive); + } else if (len >= 0) { + body = new FixedStream(c, len, keepAlive); + } else { + body = new EofStream(c); + } + return new Response(code, statusLine, h, body, len, chunked); + } + + static String readLine(InputStream in) throws IOException { + StringBuilder sb = new StringBuilder(64); + int b; + while ((b = in.read()) >= 0) { + if (b == '\n') { + int n = sb.length(); + if (n > 0 && sb.charAt(n - 1) == '\r') sb.setLength(n - 1); + return sb.toString(); + } + sb.append((char) b); + if (sb.length() > 16384) throw new IOException("Header line too long"); + } + return sb.length() == 0 ? null : sb.toString(); + } + + // ------------------------------------------------------------------ body streams + + private static final class FixedStream extends InputStream { + private final Conn c; private long left; private final boolean keepAlive; private boolean closed; + + FixedStream(Conn c, long len, boolean keepAlive) { + this.c = c; this.left = len; this.keepAlive = keepAlive; + if (len == 0) finish(true); + } + + private void finish(boolean complete) { + if (closed) return; + closed = true; + if (complete && keepAlive) release(c); else c.close(); + } + + @Override public int read() throws IOException { + byte[] one = new byte[1]; + int n = read(one, 0, 1); + return n <= 0 ? -1 : one[0] & 255; + } + + @Override public int read(byte[] b, int off, int len) throws IOException { + if (left <= 0) { finish(true); return -1; } + if (closed) return -1; + int n = c.in.read(b, off, (int) Math.min(len, left)); + if (n < 0) { finish(false); throw new IOException("Unexpected end of stream"); } + left -= n; + if (left == 0) finish(true); + return n; + } + + @Override public int available() throws IOException { + if (closed) return 0; + return (int) Math.min(c.in.available(), left); + } + + @Override public void close() { finish(left == 0); } + } + + private static final class ChunkedStream extends InputStream { + private final Conn c; private final boolean keepAlive; + private long chunkLeft = 0; private boolean eof; private boolean closed; + + ChunkedStream(Conn c, boolean keepAlive) { this.c = c; this.keepAlive = keepAlive; } + + private void finish(boolean complete) { + if (closed) return; + closed = true; + if (complete && keepAlive) release(c); else c.close(); + } + + private boolean nextChunk() throws IOException { + if (eof) return false; + String line = readLine(c.in); + if (line == null) throw new IOException("Unexpected end of chunked stream"); + if (line.length() == 0) { line = readLine(c.in); if (line == null) throw new IOException("Bad chunk"); } + int semi = line.indexOf(';'); + if (semi >= 0) line = line.substring(0, semi); + try { chunkLeft = Long.parseLong(line.trim(), 16); } + catch (NumberFormatException e) { throw new IOException("Bad chunk size"); } + if (chunkLeft == 0) { + String t; + while ((t = readLine(c.in)) != null && t.length() > 0) { /* trailers */ } + eof = true; + finish(true); + return false; + } + return true; + } + + @Override public int read() throws IOException { + byte[] one = new byte[1]; + int n = read(one, 0, 1); + return n <= 0 ? -1 : one[0] & 255; + } + + @Override public int read(byte[] b, int off, int len) throws IOException { + if (closed) return -1; + if (chunkLeft == 0) { + if (!nextChunk()) return -1; + } + int n = c.in.read(b, off, (int) Math.min(len, chunkLeft)); + if (n < 0) { finish(false); throw new IOException("Unexpected end of chunk"); } + chunkLeft -= n; + if (chunkLeft == 0) { + // consume CRLF after chunk data + String crlf = readLine(c.in); + if (crlf == null) { finish(false); } + else if (crlf.length() != 0) { finish(false); throw new IOException("Bad chunk terminator"); } + } + return n; + } + + @Override public int available() throws IOException { + if (closed) return 0; + return (int) Math.min(c.in.available(), chunkLeft); + } + + @Override public void close() { finish(eof); } + } + + private static final class EofStream extends InputStream { + private final Conn c; private boolean closed; + EofStream(Conn c) { this.c = c; } + + @Override public int read() throws IOException { + if (closed) return -1; + int b = c.in.read(); + if (b < 0) close(); + return b; + } + + @Override public int read(byte[] b, int off, int len) throws IOException { + if (closed) return -1; + int n = c.in.read(b, off, len); + if (n < 0) close(); + return n; + } + + @Override public void close() { + if (!closed) { closed = true; c.close(); } + } + } + + /** Close one in-flight response's socket from another thread (used to abort streams). */ + public static void abort(Response r) { + if (r == null) return; + InputStream b = r.body; + Conn c = null; + if (b instanceof FixedStream) c = ((FixedStream) b).c; + else if (b instanceof ChunkedStream) c = ((ChunkedStream) b).c; + else if (b instanceof EofStream) c = ((EofStream) b).c; + if (c != null) { + try { c.socket.close(); } catch (Exception ignored) {} + } + } +} diff --git a/src/com/miniabs/net/Threads.java b/src/com/miniabs/net/Threads.java new file mode 100644 index 0000000..394407f --- /dev/null +++ b/src/com/miniabs/net/Threads.java @@ -0,0 +1,43 @@ +package com.miniabs.net; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** + * Background threads with big stacks. Android 2.x gives new threads ~16 KB of stack, and the + * bundled TLS handshake + certificate parsing needs far more; overflowing it kills the app. + * 256 KB is the most Dalvik allows. + */ +public final class Threads { + public static final long STACK = 256 * 1024; + + private Threads() {} + + public static Thread create(Runnable r, String name) { + Thread t = new Thread(null, r, name, STACK); + t.setDaemon(true); + return t; + } + + public static void start(Runnable r, String name) { + create(r, name).start(); + } + + public static ExecutorService pool(final String name, int n) { + ThreadFactory f = new ThreadFactory() { + private int count; + public synchronized Thread newThread(Runnable r) { return create(r, name + "-" + (++count)); } + }; + return n == 1 ? Executors.newSingleThreadExecutor(f) : Executors.newFixedThreadPool(n, f); + } + + /** Readable one-liner for any failure, including Errors such as StackOverflowError. */ + public static String describe(Throwable t) { + String m = t.getMessage(); + String cls = t.getClass().getSimpleName(); + if (m == null || m.length() == 0) return cls; + if (t instanceof java.io.IOException) return m; + return cls + ": " + m; + } +} diff --git a/test/com/miniabs/TrustTest.java b/test/com/miniabs/TrustTest.java index 73310aa..287b806 100644 --- a/test/com/miniabs/TrustTest.java +++ b/test/com/miniabs/TrustTest.java @@ -70,18 +70,18 @@ public class TrustTest { int good = Integer.parseInt(a[1]), wrongHost = Integer.parseInt(a[2]), forged = Integer.parseInt(a[3]), expired = Integer.parseInt(a[4]), self = Integer.parseInt(a[5]); - System.out.println("Bundled roots"); - List roots = new ArrayList(); - for (String r : Roots.DER_BASE64) roots.add(Trust.parse(android.util.Base64.decode(r, 0))); - check(roots.size() == 12, "12 modern root certificates bundled"); - String isrg = Trust.fingerprint(roots.get(0)).replace("\n", ":"); + System.out.println("Bundled roots (Mozilla, assets/roots)"); + String[] roots = c.getAssets().list("roots"); + check(roots.length > 100, roots.length + " root certificates bundled"); + X509Certificate isrgCert = null; + for (String n : roots) { + X509Certificate x = cert("assets/roots/" + n); + if (x.getSubjectX500Principal().getName().contains("ISRG Root X1")) isrgCert = x; + } + String isrg = isrgCert == null ? "" : Trust.fingerprint(isrgCert).replace("\n", ":"); check(isrg.equals("96:BC:EC:06:26:49:76:F3:74:60:77:9A:CF:28:C5:A7:CF:E8:A3:C0:AA:E1:1A:8F:FC:EE:05:C0:BD:DF:08:C6"), - "Let's Encrypt ISRG Root X1 matches its published fingerprint"); - X509Certificate root = cert(pki + "/root.crt"), inter = cert(pki + "/int.crt"), leaf = cert(pki + "/leaf.crt"); - check(Trust.verify(new X509Certificate[]{leaf, inter}, Arrays.asList(root)) == null, - "leaf + intermediate validate up to a bundled-style root"); - check("unknown issuer".equals(Trust.verify(new X509Certificate[]{leaf}, Arrays.asList(root))), - "missing intermediate is rejected"); + "Let's Encrypt ISRG Root X1 bundled, matching its published fingerprint"); + X509Certificate inter = cert(pki + "/int.crt"), leaf = cert(pki + "/leaf.crt"); System.out.println("Unknown certificate, then trusting it"); p.setServer("https://localhost:" + good); @@ -110,7 +110,7 @@ public class TrustTest { JSONObject l = log.getJSONObject(i); if (l.getString("m").equals("PATCH") && l.getString("p").equals("/api/me/progress/pod1/ep7")) patched = true; } - check(patched, "mark-as-played PATCH works over TLS (raw socket path)"); + check(patched, "mark-as-played PATCH works over TLS"); System.out.println("Streaming proxy"); check(StreamProxy.needed(c), "https server streams through the proxy"); @@ -139,13 +139,11 @@ public class TrustTest { check(wh != null, "certificate for another host rejected, even from a trusted CA"); Exception fe = tryStatus(forged); Trust.Rejected fr = Trust.recentRejection(); - check(fe != null && fr != null && fr.reason.contains("isn't a CA"), - "forged certificate signed by a normal site certificate rejected (" + (fr == null ? "-" : fr.reason) + ")"); + check(fe != null && fr != null, "forged certificate signed by a normal site certificate rejected"); Trust.pin(c, fr.chain); check(tryStatus(forged) != null, "...and still rejected even if the user taps Trust"); Exception ee = tryStatus(expired); - check(ee != null && Trust.recentRejection() != null && Trust.recentRejection().expired() - && Util.describe(ee).contains("caducou"), + check(ee != null && Util.describe(ee).contains("caducou"), "expired certificate rejected: \"" + Util.describe(ee) + "\""); System.out.println("Self-signed server"); diff --git a/test/run_tests.sh b/test/run_tests.sh index 4cb3d14..29f53e1 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -3,12 +3,14 @@ set -e cd "$(dirname "$0")/.." OUT=build/test -JSON_JAR=/usr/share/java/com.android.json.jar +JSON_JAR=${JSON_JAR:-/usr/share/java/com.android.json.jar} # any org.json works (e.g. org.json:json from Maven) +BC_SRC=${BC_SRC:-$HOME/android-toolchain/bcsrc} rm -rf "$OUT" && mkdir -p "$OUT" S=src/com/miniabs -javac -nowarn -encoding UTF-8 -cp "$JSON_JAR" -d "$OUT" $(find test/shim -name '*.java') \ +javac -nowarn -encoding UTF-8 -cp "$JSON_JAR" -sourcepath "$BC_SRC" -d "$OUT" $(find test/shim -name '*.java') \ $S/Json.java $S/Prefs.java $S/Episode.java $S/Util.java $S/Api.java $S/App.java \ - $S/Trust.java $S/Roots.java $S/StreamProxy.java \ + $S/Trust.java $S/NetConnection.java $S/net/Http.java $S/net/CertVerifier.java $S/net/AssetRoots.java \ + $S/net/DiagLog.java $S/net/Threads.java $S/StreamProxy.java \ test/com/miniabs/ApiTest.java test/com/miniabs/TrustTest.java CP="$OUT:$JSON_JAR" PIDS="" diff --git a/test/shim/android/content/Context.java b/test/shim/android/content/Context.java index f38fecf..1519878 100644 --- a/test/shim/android/content/Context.java +++ b/test/shim/android/content/Context.java @@ -6,6 +6,8 @@ public class Context { private final HashMap prefs = new HashMap(); public static boolean online = true; public Context getApplicationContext() { return this; } + private final android.content.res.AssetManager assets = new android.content.res.AssetManager(); + public android.content.res.AssetManager getAssets() { return assets; } public synchronized SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferences p = prefs.get(name); if (p == null) { p = new SharedPreferences(); prefs.put(name, p); } diff --git a/test/shim/android/content/res/AssetManager.java b/test/shim/android/content/res/AssetManager.java new file mode 100644 index 0000000..1f15916 --- /dev/null +++ b/test/shim/android/content/res/AssetManager.java @@ -0,0 +1,10 @@ +package android.content.res; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +/** Desktop shim: the app's assets/ folder, read from the working directory. */ +public final class AssetManager { + public String[] list(String path) { String[] l = new File("assets", path).list(); return l == null ? new String[0] : l; } + public InputStream open(String name) throws IOException { return new FileInputStream(new File("assets", name)); } +} diff --git a/tools/gen_roots.py b/tools/gen_roots.py deleted file mode 100644 index c5e863a..0000000 --- a/tools/gen_roots.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Generates src/com/miniabs/Roots.java: modern RSA root certificates that Android 2.3's -built-in list lacks (Let's Encrypt, Google Trust Services, Sectigo/ZeroSSL, ...). - -Only RSA roots: Android 2.x's TLS 1.0 can't use ECDSA certificates anyway. -Run: python3 tools/gen_roots.py /etc/ssl/certs -""" -import base64 -import hashlib -import os -import re -import sys - -CERT_DIR = sys.argv[1] if len(sys.argv) > 1 else "/etc/ssl/certs" -NAMES = [ - "ISRG_Root_X1", # Let's Encrypt - "GTS_Root_R1", # Google Trust Services (also Cloudflare) - "USERTrust_RSA_Certification_Authority", # Sectigo, ZeroSSL - "DigiCert_Global_Root_G2", - "Amazon_Root_CA_1", - "Microsoft_RSA_Root_Certificate_Authority_2017", - "SSL.com_Root_Certification_Authority_RSA", - "Buypass_Class_2_Root_CA", - "HARICA_TLS_RSA_Root_CA_2021", - "Certum_Trusted_Network_CA", - "Go_Daddy_Root_Certificate_Authority_-_G2", - "Starfield_Root_Certificate_Authority_-_G2", -] - -out = [] -for n in NAMES: - pem = open(os.path.join(CERT_DIR, n + ".pem")).read() - b64 = "".join(re.search(r"-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", pem, re.S).group(1).split()) - der = base64.b64decode(b64) - out.append((n, b64, hashlib.sha256(der).hexdigest())) - -lines = [ - "package com.miniabs;", - "", - "/** Generated by tools/gen_roots.py from the system CA store. Do not edit. */", - "final class Roots {", - " private Roots() {", - " }", - "", - " static final String[] DER_BASE64 = {", -] -for n, b64, sha in out: - lines.append(" // %s sha256=%s" % (n, sha)) - chunks = [b64[i:i + 100] for i in range(0, len(b64), 100)] - lines.append(" " + "\n + ".join('"%s"' % c for c in chunks) + ",") -lines += [" };", "}", ""] -open(os.path.join(os.path.dirname(__file__), "..", "src", "com", "miniabs", "Roots.java"), "w").write("\n".join(lines)) -print("%d roots written" % len(out))