From f7428287eb9c5f86c94f01357f0959c381e26916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9?= Date: Sat, 17 Oct 2026 19:14:06 +0200 Subject: [PATCH] GingerCal 1.4: bundled TLS 1.2, no bridge needed CalDAV now goes through Minamp's own HTTP/TLS client (Bouncy Castle TLS 1.2, Mozilla roots, keep-alive) instead of Apache HttpClient on Android 2.3's TLS 1.0, as the iPod version does with BearSSL. The phone reaches the normal https:// Nextcloud address at home and away; a pinned self-signed server or the old bridge still works. - NetHttp transport; requests run on big-stack threads (the handshake overflows AsyncTask/IntentService stacks on Android 2.x) - http:// -> https:// redirects on the same host are followed; other hosts still refused - bridge wording replaced in Galician and English; README updated - NetTest (TLS + CalDAV discovery through the new transport) replaces BridgeTest Co-Authored-By: Claude Opus 5.5 --- AndroidManifest.xml | 4 +- README.md | 20 +- android/org/gingercal/ApacheHttp.java | 177 ------ android/org/gingercal/App.java | 10 +- android/org/gingercal/GingerApp.java | 11 +- android/org/gingercal/NetHttp.java | 78 +++ android/org/gingercal/SettingsActivity.java | 7 +- android/org/gingercal/net/AssetRoots.java | 50 ++ android/org/gingercal/net/CertVerifier.java | 323 ++++++++++ android/org/gingercal/net/DiagLog.java | 47 ++ android/org/gingercal/net/Http.java | 588 ++++++++++++++++++ android/org/gingercal/net/Threads.java | 43 ++ ...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 +- src/org/gingercal/Dav.java | 21 +- src/org/gingercal/L.java | 34 +- src/org/gingercal/Tls.java | 81 +-- stubs/android/content/Context.java | 1 + stubs/android/content/ContextWrapper.java | 1 + stubs/android/content/res/AssetManager.java | 7 + test/org/gingercal/BridgeTest.java | 99 --- test/org/gingercal/I18nTest.java | 2 +- test/org/gingercal/NetTest.java | 55 ++ test/org/gingercal/RedirectTest.java | 20 +- tools/check_i18n.py | 1 + 145 files changed, 1278 insertions(+), 407 deletions(-) delete mode 100644 android/org/gingercal/ApacheHttp.java create mode 100644 android/org/gingercal/NetHttp.java create mode 100644 android/org/gingercal/net/AssetRoots.java create mode 100644 android/org/gingercal/net/CertVerifier.java create mode 100644 android/org/gingercal/net/DiagLog.java create mode 100644 android/org/gingercal/net/Http.java create mode 100644 android/org/gingercal/net/Threads.java 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 stubs/android/content/res/AssetManager.java delete mode 100644 test/org/gingercal/BridgeTest.java create mode 100644 test/org/gingercal/NetTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7f7cdc5..c9a690a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="5" + android:versionName="1.4"> diff --git a/README.md b/README.md index c121d33..500bd86 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ A small native calendar client for Gingerbread (API 9/10). Built for a Samsung Galaxy Mini (GT-S5570, 240×320, Android 2.3.5) talking to Nextcloud, reached through Pangolin. +**Since 1.4 it brings its own TLS 1.2** (Bouncy Castle, the same client as Minamp, with Mozilla's +root certificates and keep-alive connections), like the iPod version does with BearSSL. Put the +normal `https://` Nextcloud address in Settings, at home or away: no bridge is needed. The bridge +sections below are kept for reference; a bridge address (pinned certificate) still works. + **What it does** - Month view: a 6-week grid with a coloured mark per event (calendar colour), today outlined, @@ -19,11 +24,12 @@ A small native calendar client for Gingerbread (API 9/10). Built for a Samsung G they can be deleted on the phone but edited only in Nextcloud - Pick which calendars to show and which one new events go to - Background sync every 1–24 h, plus phone notifications N minutes before timed events -- Certificate pinning, so a self-signed "legacy" endpoint is still safe against interception +- Its own TLS 1.2 stack: connects to modern `https://` servers directly +- Certificate pinning, so a self-signed server (or the old legacy-TLS bridge) is still safe against interception It does not feed the stock Samsung calendar: 2.3 has no public calendar-provider API for that. -## Why HTTPS breaks on 2.3, and the fix +## Why HTTPS broke on 2.3, and the old fix (not needed since 1.4) Three separate problems, all fatal for a normal Pangolin/Traefik URL: @@ -161,7 +167,9 @@ Tests (desktop JVM, need a CalDAV server such as Radicale on :5232 with user `xo ```sh javac -d out src/org/gingercal/*.java test/org/gingercal/*.java java -cp out org.gingercal.CoreTest # discovery, sync, recurrence, create/edit/delete -java -cp out org.gingercal.RedirectTest # forced-HTTPS / redirect-loop handling (no server needed) +java -cp out org.gingercal.RedirectTest # redirect handling (no server needed) +# the bundled TLS transport (needs the android.jar and Bouncy Castle sources to compile): +# java -cp out org.gingercal.NetTest assets/roots http://127.0.0.1:5232 xose secret java -cp out org.gingercal.I18nTest # Galician dates, titles and messages ``` @@ -172,7 +180,8 @@ declarations. ## Layout - `src/` CalDAV core, platform-neutral: discovery, PROPFIND/REPORT, iCalendar, sync, pinning -- `android/` activities, sync service, alarms, SQLite cache, Apache HttpClient transport +- `android/` activities, sync service, alarms, SQLite cache, `NetHttp` transport +- `android/org/gingercal/net/` the bundled TLS 1.2 HTTP client from Minamp; `assets/roots/` Mozilla roots - `stubs/` compile-only Android API 10 signatures (never packaged) - `bridge/` legacy-TLS endpoint: standalone nginx (config, compose file), `npm/http.conf` for Nginx Proxy Manager, certificate script @@ -181,6 +190,9 @@ declarations. ## Security notes +With the bundled TLS 1.2 the connection to Nextcloud is checked like a browser would: host name, +dates, and a chain up to a Mozilla root. The notes below are about the old bridge. + TLS 1.0 with AES-CBC is old but, combined with a pinned certificate, still keeps the connection private and authenticated against anyone without the bridge's private key. The bridge exposes only the DAV endpoint. Use an app password so you can revoke the phone from Nextcloud. If the diff --git a/android/org/gingercal/ApacheHttp.java b/android/org/gingercal/ApacheHttp.java deleted file mode 100644 index 1160994..0000000 --- a/android/org/gingercal/ApacheHttp.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.gingercal; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.URI; -import java.security.GeneralSecurityException; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.conn.scheme.LayeredSocketFactory; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; - -/** - * Android 2.3 transport. HttpURLConnection refuses PROPFIND/REPORT there, so this uses the - * bundled Apache HttpClient, plus a TLS layer that supports certificate pinning (needed for a - * self-signed legacy-TLS bridge) and SNI where the platform offers it. - */ -public class ApacheHttp implements Http { - final DefaultHttpClient client; - final Tls.PinTrust trust; - - public ApacheHttp(String pin, int timeoutMs) throws GeneralSecurityException { - trust = new Tls.PinTrust(pin); - HttpParams p = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(p, timeoutMs); - HttpConnectionParams.setSoTimeout(p, timeoutMs); - HttpConnectionParams.setStaleCheckingEnabled(p, true); - HttpProtocolParams.setUseExpectContinue(p, false); - HttpProtocolParams.setUserAgent(p, "GingerCal/1.0 (Android 2.3)"); - p.setBooleanParameter("http.protocol.handle-redirects", false); - SchemeRegistry reg = new SchemeRegistry(); - reg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - reg.register(new Scheme("https", new TlsFactory(trust), 443)); - client = new DefaultHttpClient(new ThreadSafeClientConnManager(p, reg), p); - } - - static class Req extends HttpEntityEnclosingRequestBase { - final String m; - - Req(String m, URI u) { - this.m = m; - setURI(u); - } - - @Override - public String getMethod() { - return m; - } - } - - public Resp exec(String method, String url, String[] headers, String body, String contentType) - throws IOException { - Req r = new Req(method, URI.create(url)); - for (int i = 0; i + 1 < headers.length; i += 2) r.addHeader(headers[i], headers[i + 1]); - if (body != null) { - StringEntity e = new StringEntity(body, "UTF-8"); - e.setContentType(contentType); - r.setEntity(e); - } - trust.reset(); - HttpResponse res; - try { - res = client.execute(r); - } catch (IOException e) { - throw trust.translate(e); - } - Resp out = new Resp(); - out.code = res.getStatusLine().getStatusCode(); - Header h = res.getFirstHeader("ETag"); - if (h != null) out.etag = h.getValue(); - h = res.getFirstHeader("Location"); - if (h != null) out.location = h.getValue(); - HttpEntity en = res.getEntity(); - if (en != null) { - InputStream in = en.getContent(); - try { - ByteArrayOutputStream bo = new ByteArrayOutputStream(); - byte[] buf = new byte[8192]; - int n; - while ((n = in.read(buf)) > 0) bo.write(buf, 0, n); - out.body = new String(bo.toByteArray(), "UTF-8"); - } finally { - en.consumeContent(); - } - } - return out; - } - - public void close() { - client.getConnectionManager().shutdown(); - } - - // ------------------------------------------------------------------ TLS - - static class TlsFactory implements LayeredSocketFactory { - final SSLSocketFactory f; - final Tls.PinTrust trust; - - TlsFactory(Tls.PinTrust trust) throws GeneralSecurityException { - this.trust = trust; - SSLContext c = SSLContext.getInstance("TLS"); - c.init(null, new TrustManager[] {trust}, null); - f = c.getSocketFactory(); - } - - public Socket createSocket() throws IOException { - return f.createSocket(); - } - - public Socket connectSocket(Socket sock, String host, int port, InetAddress local, int localPort, - HttpParams params) throws IOException { - SSLSocket s = (SSLSocket) (sock != null ? sock : createSocket()); - if (local != null || localPort > 0) { - s.bind(new InetSocketAddress(local, localPort < 0 ? 0 : localPort)); - } - sni(s, host); - s.connect(new InetSocketAddress(host, port), HttpConnectionParams.getConnectionTimeout(params)); - s.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); - s.startHandshake(); - verifyHost(s, host); - return s; - } - - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { - SSLSocket s = (SSLSocket) f.createSocket(socket, host, port, autoClose); - sni(s, host); - s.startHandshake(); - verifyHost(s, host); - return s; - } - - public boolean isSecure(Socket sock) { - return true; - } - - void verifyHost(SSLSocket s, String host) throws IOException { - if (trust.pinned()) return; // the pin identifies the exact certificate - if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(host, s.getSession())) { - s.close(); - throw new SSLException("Certificate is not valid for " + host); - } - } - - /** Gingerbread's OpenSSLSocketImpl has a hidden setHostname() for SNI. */ - static void sni(SSLSocket s, String host) { - if (host.matches("[0-9.]+") || host.indexOf(':') >= 0) return; - try { - Method m = s.getClass().getMethod("setHostname", String.class); - m.invoke(s, host); - } catch (Throwable ignored) { - } - } - } -} diff --git a/android/org/gingercal/App.java b/android/org/gingercal/App.java index d17a745..a30b3a3 100644 --- a/android/org/gingercal/App.java +++ b/android/org/gingercal/App.java @@ -67,7 +67,7 @@ public final class App { } public static Dav dav(String url, String user, String pass, String pin) throws Exception { - return new Dav(new ApacheHttp(pin, 30000), url, user, pass); + return new Dav(new NetHttp(pin, 30000), url, user, pass); } public static boolean online(Context c) { @@ -126,9 +126,9 @@ public final class App { if (t instanceof UnknownHostException) { return L.f("Can't resolve %s. No connection, or a typo in the server address.", t.getMessage()); } - if (t instanceof ConnectException) return L.f("Can't connect to the server (%s). Wrong port, or the bridge is down?", t.getMessage()); + if (t instanceof ConnectException) return L.f("Can't connect to the server (%s). Wrong address or port, or the server is down?", t.getMessage()); if (t instanceof SocketTimeoutException) return L.t("The server took too long to answer."); - if (t instanceof SSLException) return L.f("TLS error: %s", t.getMessage()); + if (t instanceof SSLException || t instanceof com.minamp.bc.tls.TlsException) return L.f("TLS error: %s", t.getMessage()); if (t instanceof IOException) return L.f("Network error: %s", t); if (t instanceof OutOfMemoryError) return L.t("Out of memory. Lower 'Days ahead' in Settings."); return L.f("Error: %s", t); @@ -144,8 +144,8 @@ public final class App { public static void askTrust(Activity a, final DavException.UntrustedCert u, final Trusted cb) { String msg = (u.pinMismatch - ? L.t("WARNING: the server certificate changed since you trusted it. That happens when you regenerate the bridge certificate - or when someone is intercepting the connection.\n\n") - : L.t("This phone doesn't trust the server certificate (normal for the self-signed legacy bridge, or for Let's Encrypt on Android 2.3).\n\n")) + ? L.t("WARNING: the server certificate changed since you trusted it. That happens when a self-signed certificate is regenerated - or when someone is intercepting the connection.\n\n") + : L.t("The server certificate isn't signed by a known authority (normal for a self-signed server or an old TLS 1.0 bridge).\n\n")) + L.f("Subject: %s\nIssuer: %s\nExpires: %s\n\nSHA-256:\n%s\n\nOnly trust it if this matches the fingerprint printed on your server.", u.subject, u.issuer, u.expires, u.fingerprint); new AlertDialog.Builder(a) diff --git a/android/org/gingercal/GingerApp.java b/android/org/gingercal/GingerApp.java index 597a660..c3d2cd7 100644 --- a/android/org/gingercal/GingerApp.java +++ b/android/org/gingercal/GingerApp.java @@ -2,11 +2,20 @@ package org.gingercal; import android.app.Application; -/** Runs before any screen, service or alarm: sets the interface language (Galician by default). */ +/** Runs before any screen, service or alarm: interface language (Galician by default), bundled TLS. */ public class GingerApp extends Application { @Override public void onCreate() { super.onCreate(); L.gl = !"en".equals(getSharedPreferences("gingercal", 0).getString("lang", "gl")); + org.gingercal.net.CertVerifier.setRootStore(new org.gingercal.net.AssetRoots(this)); + org.gingercal.net.Http.USER_AGENT = "GingerCal/1.4 (Android 2.3)"; + tlsTexts(); + } + + /** The bundled TLS stack's messages, in the current language. */ + static void tlsTexts() { + org.gingercal.net.CertVerifier.msgMismatch = L.t("The server certificate is for a different address (%1$s)."); + org.gingercal.net.CertVerifier.msgDates = L.t("The server certificate has expired, or the phone's date is wrong."); } } diff --git a/android/org/gingercal/NetHttp.java b/android/org/gingercal/NetHttp.java new file mode 100644 index 0000000..c6a5398 --- /dev/null +++ b/android/org/gingercal/NetHttp.java @@ -0,0 +1,78 @@ +package org.gingercal; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +import org.gingercal.net.CertVerifier; + +/** + * Android 2.3 transport through the bundled TLS 1.2 client (org.gingercal.net, from Minamp): + * the phone's own TLS 1.0 can't reach Nextcloud, so this talks to the normal https:// address + * directly, any method (PROPFIND/REPORT included), with keep-alive. A pinned certificate (a + * self-signed server or an old bridge) is still honoured. + */ +public class NetHttp implements Http { + /** AsyncTask and IntentService threads have Android 2.x's small default stack, which the + * TLS handshake overflows: requests run here, on big-stack threads, and the caller waits. */ + private static final ExecutorService NET = org.gingercal.net.Threads.pool("net", 3); + + final String pin; + final int timeoutMs; + + public NetHttp(String pin, int timeoutMs) { + this.pin = pin == null ? "" : pin; + this.timeoutMs = timeoutMs; + } + + public Resp exec(final String method, final String url, final String[] headers, final String body, + final String contentType) throws IOException { + try { + return NET.submit(new Callable() { + public Resp call() throws IOException { + return execHere(method, url, headers, body, contentType); + } + }).get(); + } catch (ExecutionException e) { + Throwable c = e.getCause(); + if (c instanceof IOException) throw (IOException) c; + if (c instanceof RuntimeException) throw (RuntimeException) c; + if (c instanceof Error) throw (Error) c; + throw new IOException(String.valueOf(c)); + } catch (InterruptedException e) { + throw new IOException(L.t("Interrupted")); + } + } + + Resp execHere(String method, String url, String[] headers, String body, String contentType) + throws IOException { + CertVerifier.setPin(pin); + Map h = new LinkedHashMap(); + for (int i = 0; i + 1 < headers.length; i += 2) h.put(headers[i], headers[i + 1]); + byte[] data = null; + if (body != null) { + data = body.getBytes("UTF-8"); + if (contentType != null) h.put("Content-Type", contentType); + } + org.gingercal.net.Http.Response r; + try { + // one hop at a time: Dav decides which redirects are safe to follow + r = org.gingercal.net.Http.requestNoRedirect(method, url, h, data, timeoutMs); + } catch (CertVerifier.Untrusted u) { + throw new DavException.UntrustedCert(u.fingerprint, u.subject, u.issuer, u.expires, u.changed); + } + Resp out = new Resp(); + out.code = r.code; + out.etag = r.header("etag"); + out.location = r.header("location"); + out.body = r.readString(); + return out; + } + + public void close() { + // pooled connections are shared and expire by themselves + } +} diff --git a/android/org/gingercal/SettingsActivity.java b/android/org/gingercal/SettingsActivity.java index 9f2b1ab..786ef7a 100644 --- a/android/org/gingercal/SettingsActivity.java +++ b/android/org/gingercal/SettingsActivity.java @@ -31,8 +31,8 @@ public class SettingsActivity extends Activity { LinearLayout f = App.vbox(this); int p = App.dp(this, 8); f.setPadding(p, p, p, p); - f.addView(App.text(this, L.t("Nextcloud server. Android 2.3 can't speak TLS 1.2, so use either your legacy-TLS bridge (https://cloud.example.com:8443) or http:// to the server on your home Wi-Fi. Use an app password, not your real one."), 12, 0xFFB0BEC5)); - url = field(f, L.t("Server address"), "https://cloud.example.com:8443", 1 | 0x10 /* URI */); + f.addView(App.text(this, L.t("Nextcloud server: your normal https:// address. This app brings its own modern TLS, so no bridge is needed, at home or away. Use an app password, not your real one."), 12, 0xFFB0BEC5)); + url = field(f, L.t("Server address"), "https://cloud.example.com", 1 | 0x10 /* URI */); user = field(f, L.t("User name"), "", 1 | 0x80000 /* no suggestions */); pass = field(f, L.t("App password"), "", 1 | 0x80 /* password */); pin = field(f, L.t("Pinned certificate SHA-256 (filled in for you)"), L.t("optional"), 1 | 0x80000); @@ -99,6 +99,7 @@ public class SettingsActivity extends Activity { save(); App.prefs(SettingsActivity.this).edit().putString("lang", toGl ? "gl" : "en").commit(); L.gl = toGl; + GingerApp.tlsTexts(); finish(); // reopen so every label is redrawn in the new language startActivity(new Intent(SettingsActivity.this, SettingsActivity.class)); } @@ -188,7 +189,7 @@ public class SettingsActivity extends Activity { } if (Dav.normalize(u).startsWith("http://") && !App.isLan(u)) { new AlertDialog.Builder(this).setTitle(L.t("Unencrypted connection")) - .setMessage(L.t("http:// sends your password in clear text over the internet. Use the legacy-TLS bridge instead, or only use http:// to a LAN address.")) + .setMessage(L.t("http:// sends your password in clear text over the internet. Use the https:// address instead, or only use http:// to a LAN address.")) .setPositiveButton(L.t("Use anyway"), new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { save(); diff --git a/android/org/gingercal/net/AssetRoots.java b/android/org/gingercal/net/AssetRoots.java new file mode 100644 index 0000000..e89f649 --- /dev/null +++ b/android/org/gingercal/net/AssetRoots.java @@ -0,0 +1,50 @@ +package org.gingercal.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/android/org/gingercal/net/CertVerifier.java b/android/org/gingercal/net/CertVerifier.java new file mode 100644 index 0000000..40acfc7 --- /dev/null +++ b/android/org/gingercal/net/CertVerifier.java @@ -0,0 +1,323 @@ +package org.gingercal.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 + + Untrusted(String fp, String subject, String issuer, String expires, boolean changed) { + super("untrusted certificate " + fp); + 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(byte[] leafDer, boolean changed) { + 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); + } + + /** 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.get(0), 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 { + 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) { + for (int i = 0; i < anchors.size(); i++) { + Certificate root = Certificate.getInstance(anchors.get(i)); + if (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(chainDer.get(0), 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/android/org/gingercal/net/DiagLog.java b/android/org/gingercal/net/DiagLog.java new file mode 100644 index 0000000..37224b7 --- /dev/null +++ b/android/org/gingercal/net/DiagLog.java @@ -0,0 +1,47 @@ +package org.gingercal.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/android/org/gingercal/net/Http.java b/android/org/gingercal/net/Http.java new file mode 100644 index 0000000..acc2491 --- /dev/null +++ b/android/org/gingercal/net/Http.java @@ -0,0 +1,588 @@ +package org.gingercal.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/android/org/gingercal/net/Threads.java b/android/org/gingercal/net/Threads.java new file mode 100644 index 0000000..887d9bc --- /dev/null +++ b/android/org/gingercal/net/Threads.java @@ -0,0 +1,43 @@ +package org.gingercal.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/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 zcmXqLV&O7qV*0dznTe5!iId?_oimG{yS=0VFB_*;n@8JsUPeZ4RtAH{CPQumPB!LH z7B*ofk5EHF1AY*PorlAzG$pYhwYbDk%s>Pr#Kptwo0(jcU!0n(;8T*KXQ*zV3KD1L zk%Nn;>IPS3rsw4=1m)+KC^$PR7#SE?8p<2U!Y$=w6cfnIOUrji)hz}q)ypU~kQ3)M zurx3;G&3|bHZZh^66ZCtG&BM-A>6^?$R&DPxD)+-Z3W`CQ&T6rMZ9DQMQ-($*546@0Rj zw@Ga8(OGf+cTC~VTW|iosOx{CFS}_)Y5R@s3!hq_pXudxp;@x_z(OIJskvTXZX93g zYpmC5=}=lHQqv$bBjC7W`i=z+i6P5!{Nx|Zx4@4)X!V90vRuqUwbLE@TXMs?W9LV>5R4 zT=1WJ>#wa$%!~|-iyIpZ>cB}zR+y3TKMSh?GmtXi111z%evkkQFg>yX(*uyt$0Eie za+?3xeJAHLwx_pxC@++%TBfzzB*Q=+B(2OMVIbCkU4b-6fgp>Bflwo6YC=unz(`_b zIC+sTPVefO%{6wX|JeTL)tEY&<;4D5XDfF&Px!E8-}_JR6=&?ZawV4S+dHQ>2005> ztvvN{eb+C0?&2)FS9%V0^6NqpD_`qzm|s`^+%tcl?8b*nN^dWC6e6;5myG5TiEl!? z7Kw;FHZ(oq@PGRQ*0S(}GLfHt?0@oBH7Ta$LtUNx=6yf6D+-=rW(+G>E-*<>+N4o# z?HsAYEH-bM60gazeU7(k|D^rVwf(8>nv2K7TlQwJyL02`HtD1SZd)FR?K>|cBM&Qs zftw+>0Vf-CC<~h~las5Vq=7hy!zC=>o|B)Hm=m0to~Mvkrdyn7C}SW6l42GXMv@B3 z&o5C3GBGod6X!LwG%z+YfPg4*UZ5a?OEr6%n3Rycz{twL+{DDsV9>3OgzC}8s>Wn?tFE@YRikKhT9*N&_)Dcs zPvbb>Q~g-`z}%lApQJyAWr^+AX>C1QVKM8qQx=nnM6|3(DNugKymwMsJ`^Owh7WjUfD0auKCtfqzFtU*6M*g8~Mzz16 z#Re~{6aBNhv?lh=k8k2KZ;oe6p8j0@L7RQ)&78`RvYfdx%kubMR`RS+&RTq5+Q4xB z#*pfNS*O!h-ATQ$Xr9+BcE#+*{@X_)f8A3$z%u9Ys%W#xFKqW#Eu70;wvqez+`s2% ztYACzLu>KE<^|hTi}KrsB;vCY{~Ub(6GX zE${5RA-l-Q*!SoAC0f=lnR07xx-Ry!S$;z$a8roPWWOWtt0%W6sJ-I!c<^=i@s}Bw zc4rwDo~c|XxZ=aizfa}&=&v{v{<`dl;xhN(P~VLGtV`ZqS6iKQ%h}xf(Xp*hH#JLI z9lw0rcOFO8=XdArl76pwk#OeX@r5zXu18$Yd=}3ZImyCgcS&Vg>InnMQ$~}`qvZs@`B&WGv$UGT?DIQHriblaTS$FQW3s%+V@;c^Fgyu z0$ceya(6roo8NhFTAHPMicX&Ix}@s;2_=#{!q!Y$BWCn=ZAR$78M`i<-1~Ae@i1T8 zH}95&cf8eGjUp;1hUzprZ!VFqTBSNq_WHu^ooAi&SeB$U`*xac%UNOORjF8^birxQ zMF+zP3-_^{Kb8LW>xYKR2`8JilvppYTL28cgrEPX zyXSq(0jbs7=BpgLU!37FK}Y0^eUQ|XOQ%H+CuW|1^i$&KF5~$ki&ZyYWCVVriQB;Crqe9rT`OZdy2)4H5L&Hv5gRFQn~ z5r2O5m4&t?(_6QDSUO%)_;bN(^RE~AN4jV0Xz%f_kI=F#?@mywZ|mBFB~ z!I0a4lZ`o)g-w_#z{gO@Kmo+z5|#|j%dAQ*$yW$TP0q;6&&f~EOf6Oj)^pS|R5wrs zso@rub51QPDa};?QJHC($%!SI`FRSCr6n2pMVTd)hAIY1Acf4r(olsVMWw|hsVNG6 zsU_w4McE3@jtWKwN)r4=28M=)1_ovZ7RHvQW>Fvk6Eh@%!Qjj$CMD#+V`ODuZerqR zFlb`pVrpVyWZ3(&;>WYA4Lm7x+oY$Zq}wZ5$(1tJzW*JQ-gELyO!mr&&vrUJRq&pD zYj*X{nXxx#H+WxfanM%3{9>2EZaYq?<(^aDYdfeMntpuwyV*Z;e_t_m1Yt zN2ctr3w*t<9o!tYW1*wP?2Eq*>-!(XKip`~-zVmoaGkAJ;^!(gfz?`3bKZj)%5Rosmy2oLKC{+ZW=Z=Ux8gX-qzu>aCli{S zb~|1Bkoe%>V%xOEv0o(Kb)4)Kch#I}RLW%dPE+kfHq*1MNv`$gqEX5jomcGc`KfRC z;J~xTSNyc^qRny_*rM)!n|waJbmrGzZBpqKoA*!qXL4un^PsS&o1d0eUw53ydM7sX z@l;;toqw9ASx&9aX^8R{E;@F-RPJN-P6f)^?JdN_MpGF z-meMNW8due?*iwog7-6ig+0r?y*1qX&Ql##(G&CZD*S%jnlrCW=T!J-si!9C%u7dsg^81MrVy{s@J<9`-b z17;v)APW-UV-aH!*|spmlIidQ`@XI{zpng{S$5Hi@2>$LNSYs{o&}g`*^pBiFpC0H z86!jCQoeIfUhcQIxY`+Ygemd2&ruO^-ERNjYyt5gHuPusdwIHpk~m#?u&P`b{dATgUe4ZLguych4XC* zCvW|3(4Xg%&{AQmm~!p6qr3$F^OL28d-ulNGikST+`#v3=QVpy;b|iJ_h!jP?F>6- zYPBK0z&Gx(=0|0XqR4Hl5A~#}$&~A;m6>*HEYDo<)B3CO-By8;lM|T3CO%)k%l(Sb zbU~%Zi#(^~Jl{UWdzbE#TgKf!%Uu3P*&LHixv|yo$ugU&72({s-zG|({&=pqJb6ib zzE7aVwI}h%=WlD&deZ7q84+jw<2F-M`a#RvhfBO|&E$S+PVUHvUg09C_+j~rMqZs4 zlg+l6Z#uO>TCHl9s{E{34fDLU?0+ww^{?2d@X)+tMGw0&)IUDq-*UufY2xG`(*@YI z3{+R{JW;sgea0Exx~~uF_?uSbZ`+mA>|B1fYm)fFdC?VOwyuwii|#&5J2mt2$)jq2 zc{5nt)@{2vyJw%|zKzE|Nj{EYI;C?X-;4d`Nz+>nrJs&0m&~8%Brv1&+0^cYJ#UW+ zUGF+~bw*6sH0Go4%Gpf+pMUbsUO4gH?OVF7AI`3uyGer6_3)&HMdE+lw=Lk-`FP}> ba|TCNy!D5&)FR?K>|cBR4C9fv+LA0Vf-CC<~h~lZ&gN zsDUtu!^Oknq8n0FT3n*wo}1)hsBWMN5@zO+0}2O)28SpF<>!|uIOil57b_SmI6EpB zDHs_TSQ^NQ^BPzh8XA}ySQwj{T11KS8XtyH| z{>xjW-nyGcdA*%e%%XT<)m|RqtqiBsa*ky4Oo^I0EqzA2@D*wRE?#7bCr=pG;HoWkdHGfLxKgqi{ifbBAO}p3kcC!&{YKzIUXMe*5 zw#Jw3*WVz(dz6Wpk%4h>6QdL`M8pjEfpI7+%*gnkh1Gx=NEyh21o&9QSVaEb5;-#8 z;GW`MIVa0MtCN*IAAD>v-~&nXgVeJC(+`_L<7JS5JWJzggT@mEjSUOx7Syy=<)kDQ zSn2DhWEQ0+m*f{!>ZRzGfKrZLUTTTHv!5+A#i$w?fYJ<*0;d`vZ46>N0tq7!2Bsbz ze~@C3UZ4Od-RU^ny13dWrxulDre!84mZTP?mgNKK%>2BZ%;FLQ=LL=n?AvTJN=iU3 zC@(KZu{*gaM<42z_#&Xy@yVd%8gCq*oEUEu53*1Xs02Cv05b$I{V+1rPrq$&;{K93 zwTe{@1?NPBGCM@3Cb%%J+4R!De|Es3v>SE323rdZUp~q_{<+|`f@s?X-VgiMFqXEQ ztDEn;e|v)UoX@9ZAH`+dSUh+7Q-_30W{UqVY}p}xt7LKgmHQiJFs;eY⪙?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 zcmXqLV#+gUV*I>-nTe5!Ng(N)QO?sNk27MVlkU#Zn>*Eji;Y98&EuRc3p2Apg(0^A zCmVAp3!5;LYp|i90Y8Yt#lzv|=NqCMPAy6;Hk2?B1Mw4i_`@7S zT!VE3jVz4J3=B-X40Q~&Kr+lc%8t$oL5`kL3NS+yf?b2cJYD>QT!Vp3_s}5!U;{aE zUPB8*BO?O?OJj3m<0x@n6JsRq0J^NqK!A-M970TtY^>UiEDTD_NenFCc7+F@IJNq| znOBhFlj)b5)8~Ci4ZIN7e%yUy=8)1uCRmyJ`a&7fY7N;f_C1)sj z<`tJ@mXwxc=I0eFI6E4u7$|`>GV@4#I0kt-J1T_u1SR+S85+om z^8!gDLrX&gQ&VG$C~;mBQy|wE%r%dqvum1|l#qjik(GhDiHV=Vpoxi#sfme^q5IJD z^{KO2)2E%}RoQugFXhjn)&QBCGOR}|K zc^KIAf6b*WnRoTx2rhPgmz{0xv%jZziQHVloi8nZR`K!7c%&?Q(J%SR^Bs;Zzq8k8 z-;%$4bYe&JlFmsN8=v)9Ml86N>AZ67jG&0T+g!i1&u9F4yd=)bwRi^mt9YiOdpt`w z&tJDvcDwB6KV6Z}^NLc}el?h)%>KDa%-eLvoZ#L1r=CRJJl-q&XU5FmI!l_eKWNGK zamN?N#H@3w*6eIN&AfQ#sg8Nv4xEn0x1U8+{arV+;6N1rHQ}~G{j$tI6Yk#D&Aa$% z_E)LwdHJF}RUf=H7ARRXsN|R}vHoN*@pFan`GfYRt3S#LDsOdGn#VY4y9Coy!HZ3| zMH$W{njh4vnf36(uFU~%@0!-kHT>~g`4KaNN5NuFwMx<8gwGZ+Yb{*H= zeS3i`yOc5K58o}znU-aKITXp-k=h!z&inL(dp5PYQYT*imS3FcbxlinZMlz>#O#Uu z5jWUEj!yrv%3h&sS?jX$Z_ED3JbITm#aOF3poQzHf7_1$*9wNl*U=e|)q2lV#{_;6 z(Q()(l^|PHlqf5otlP2b+#=Tr9DlO+Z4S_i`n$qI!tL4whe;E-TH-Pcf4qLnds+5a z?M#W#F!fZuUe}{4Z_YhtVrFDuTA-pG3#X3B((1?-PtEBiC)nlOb3dJv z2~c}H&({(JqjGJ&IROD`yR2YfNLmiQ8y@;Pz!iS4zH@(&gJ``Nx z<_0DY^s)Ls zwfC%Z&!0CxdZGH3^#-vP9;?aM_KDx-x8swn;5@#PsYuEDyLs8J(j%?RQOQ>~ma-n& zxp&Ii9?_Z0C)a*E`gv8!d6w2GGZ?-+`guwvb^n8;^(T)0+H`VWn8&TkzthS@_bu)V zJF?m2&p!7r2e+*FbJd_Dd+S7zpv?JB7Y{;#n=$M{=O z?{CJB*`Jo4H?)WmUG;1_!@>JeOP5=&df8HM6S%_a=9K83{r((qLSNu<2tTuXoz;*N+Oq-ab3(96RIs{Dz}z#j8Gz_ zu&hf`ibyBr(kdmFrtMIhOC=?&Sz&f`I(s_%|Nilw=Y5{v?|0tkJ?Hlze5^9UN3E2j zAP|CNHP~;SNa|w~5IO7ygY=#0OL8EH!vKgk1&3_FqI}Uc96^B zL^DH~Ff$x>4(D*ABcd6hjA&Lo>=Mn13uSXy5#fna8X5~bySczLOH)fTbHtL0Xl<6G zm?PGvW)urkQ^YD@vz(bFVu4tErT@p1d`RcpkAWBf#fJz0!iP3Odk3V+IdEB-up z(PBLH2hsRlpOK^26?&lHtIh^po-s{*nnC(!Qxu8O{(cSDV5iro2$N&`D^dC6Ma9=) zBc=(|XJ~vvt-bd#g3K5L6ZqzK0qf3oerL1DyYY@)Tb-4AVBg#>OL@K8+jmbSuU304 zT$#F0eq!vOb9kdiVYClaV_7`2vaEa@Z>8OvZy8aqYw)}@zt&(dh$%fJd*Oi}rTJV>UQ^_PnyS!d#dY*?N+CtN z)es*Nbf%7x6`%(vjgGC>dQ|Gvlm<_3%RU;ly<#f+hDfn(v+6$jTy#@mZGy*AAz#Ht_jE>Y%=W;=g~e7piRb>5DJ=?0iplD=T}U zZS}lRh)O{;NG)2W^}g(w~yoYGKc9(0g|4g(Lpt%qlMxZ7sH*|!aSAMys>HZ#^*-a z2M8^!0vRn*W$($q+Rep|DPan1n~tNqta-mGJT0WPi9TMa}+`+mo_zszD(P7hr+ zg2`3I`m^+vxf_{)k-$mhJ#=nDDJp^Pe5wrho!zcL%pb(_#krz(oia{;-NpyMS45WR zbGu4Lh<50GmHg7W?jtNoTHzaUPE5O69!cUx%=57llI@@%eiF`mO10ZhO#Qp6zeH!x z2S%ex-0pCcd$4;aS3cLg!aA?E2*Vb@l(KO!d!dK>g0GHdunIymc$-jx%is)X>pG0U$?*d5&KcEgVoMQY4PDVCp|&fT2Hvw1`L^prUV zzAV<0pUS>L{Jz0$28n4t3md)fvg8IYw*AAt=E_m?rq@rVa!6`4U2?Kod?WZxWoLp% z!il0u0e6`Do9#C_g-6}adMP;lZmY%~1E1B84{daHahHG4CB{DOk|KZbfwwE`uK2x~ z&G}}%(;94*_3z-&XExu+v-Cr?TF1r8b!8q5#a{k8Hc*R+3 zo6r1;yVk25FKz3%UZFHC%In+w->W6Q7arIqYxu@m;q~c9JHD=)wCVf97Vl4sK24sb zH1D!VzFEj#`M5~Yoy98-*5p?wy%fuFk4pW_Cpg>f=HJ{q*XA79QoD4+UHh>7g+ISN z5%T!URxcF3ZKbk*H1pvHg<8tnzi-%Ry=TVWYcp2xWPj@uN?-PAQ^XS{PGOssy9Ksf zGI0I)<~xJn{nQ_Kw4Foui%mbme^n>3WmQqzzh_o;hwN+L{m~b(d)9h4Ab-2(TP9{k z2FArs1`Y=Nz*H+M%*gnkh1Gx=NEyh21o&9QSVTURi~ovS@$Z{$;nj>?C*61~Zmi#6 zzz34%2dQTPW~MgeR0hmCz*NS_5D@sG^iBcGaY2-bjx((h^uQVSDuOg+4?K!Xz!1SBCoA@#V-UMs(yT^xX-PE%|hoz z%&oK|UzU6blQh7R|GJ4*!)r1NJL- z;>|MBESsmPt96|?V)<{DV*b^lwwLuuPpA7`P`Ti+;X)}#-`3hp8 zN4fKbD;8~PV~@5v`S87cDocaAkxRi;Kg0B_l4t?f(pMYiUwCHv&u@PB8SP1~(Rr!~ zt!`&8uG&zRHo1J-zUF-v+f@A2xX-+;3X^*9GU`~$TAwNg4;THN4!ecz_K8-^ZEf_@ z3cq(%()rfyQ_=2i&D{3ec*`n^E0y9lHprjfchNpfV21Y8_J#6uQ$!Lhbv84E>OJ|o rnJ@gvL`Q}>_Ai#+fA#JEi4SXYgI0*Io}KtdDA4qY>fiONvf3;G#O+h+ 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 "$OUT/apk/apktool.yml" < visited, int code) throws DavException { String a = originKey(from), b = originKey(to); - if (a.startsWith("http:") && b.startsWith("https:")) { - throw new DavException(DavException.REDIRECT, L.f("The server redirected http:// to %s (Nextcloud forces HTTPS / HSTS). Android 2.3 can't follow that: use the legacy-TLS bridge address instead (see README, 'Forced HTTPS / HSTS').", origin(to))); - } - if (!a.equals(b)) { - throw new DavException(DavException.REDIRECT, L.f("The server redirected to %s, not the address set in the app. Nextcloud is sending its public URL; check the bridge's proxy_redirect line (README, 'Forced HTTPS / HSTS').", origin(to))); + boolean upgrade = a.startsWith("http:") && b.startsWith("https:") && host(from).equals(host(to)); + if (!a.equals(b) && !upgrade) { + throw new DavException(DavException.REDIRECT, L.f("The server redirected to %s, not the address set in the app. Put that address in Settings.", origin(to))); } if (visited.contains(to)) { - throw new DavException(DavException.REDIRECT, L.f("Redirect loop (HTTP %d back to the same address). Nextcloud forces HTTPS but the bridge reaches it over plain HTTP: switch the bridge to proxy_pass https:// (README, 'Forced HTTPS / HSTS').", code)); + throw new DavException(DavException.REDIRECT, L.f("Redirect loop (HTTP %d back to the same address). Use the server's https:// address in Settings.", code)); } } diff --git a/src/org/gingercal/L.java b/src/org/gingercal/L.java index 070310f..4baf6b5 100644 --- a/src/org/gingercal/L.java +++ b/src/org/gingercal/L.java @@ -150,8 +150,8 @@ public final class L { // settings put("GingerCal settings", "Configuración de GingerCal"); - put("Nextcloud server. Android 2.3 can't speak TLS 1.2, so use either your legacy-TLS bridge (https://cloud.example.com:8443) or http:// to the server on your home Wi-Fi. Use an app password, not your real one.", - "Servidor Nextcloud. Android 2.3 non admite TLS 1.2, así que usa a túa ponte TLS antiga (https://cloud.example.com:8443) ou http:// ao servidor na wifi da casa. Usa un contrasinal de aplicación, non o teu contrasinal real."); + put("Nextcloud server: your normal https:// address. This app brings its own modern TLS, so no bridge is needed, at home or away. Use an app password, not your real one.", + "Servidor Nextcloud: o teu enderezo https:// de sempre. Esta aplicación trae o seu propio TLS moderno, así que non fai falta ningunha ponte, nin na casa nin fóra. Usa un contrasinal de aplicación, non o teu contrasinal real."); put("Server address", "Enderezo do servidor"); put("User name", "Usuario"); put("App password", "Contrasinal de aplicación"); @@ -173,8 +173,8 @@ public final class L { put("Re-downloading in the background", "Descargando todo de novo en segundo plano"); put("Server address and user name are required.", "O enderezo do servidor e o usuario son obrigatorios."); put("Unencrypted connection", "Conexión sen cifrar"); - put("http:// sends your password in clear text over the internet. Use the legacy-TLS bridge instead, or only use http:// to a LAN address.", - "Con http:// o contrasinal viaxa sen cifrar por internet. Usa a ponte TLS antiga ou usa http:// só cun enderezo da rede local."); + put("http:// sends your password in clear text over the internet. Use the https:// address instead, or only use http:// to a LAN address.", + "Con http:// o contrasinal viaxa sen cifrar por internet. Usa o enderezo https://, ou usa http:// só cun enderezo da rede local."); put("Use anyway", "Usar igualmente"); put("Connecting…", "Conectando…"); put("Connection failed", "Erro de conexión"); @@ -183,21 +183,25 @@ public final class L { put("Sync now", "Sincronizar agora"); // certificate trust - put("WARNING: the server certificate changed since you trusted it. That happens when you regenerate the bridge certificate - or when someone is intercepting the connection.\n\n", - "AVISO: o certificado do servidor cambiou desde que confiaches nel. Isto pasa se rexeneras o certificado da ponte, ou se alguén está a interceptar a conexión.\n\n"); - put("This phone doesn't trust the server certificate (normal for the self-signed legacy bridge, or for Let's Encrypt on Android 2.3).\n\n", - "Este teléfono non confía no certificado do servidor (é normal coa ponte antiga autoasinada ou con Let's Encrypt en Android 2.3).\n\n"); + put("WARNING: the server certificate changed since you trusted it. That happens when a self-signed certificate is regenerated - or when someone is intercepting the connection.\n\n", + "AVISO: o certificado do servidor cambiou desde que confiaches nel. Isto pasa se se rexenera un certificado autoasinado, ou se alguén está a interceptar a conexión.\n\n"); + put("The server certificate isn't signed by a known authority (normal for a self-signed server or an old TLS 1.0 bridge).\n\n", + "O certificado do servidor non está asinado por unha autoridade coñecida (é normal nun servidor autoasinado ou nunha ponte TLS 1.0 antiga).\n\n"); put("Subject: %s\nIssuer: %s\nExpires: %s\n\nSHA-256:\n%s\n\nOnly trust it if this matches the fingerprint printed on your server.", "Suxeito: %s\nEmisor: %s\nCaduca: %s\n\nSHA-256:\n%s\n\nConfía nel só se coincide coa pegada dixital que mostra o teu servidor."); put("Certificate changed!", "O certificado cambiou!"); put("Trust this certificate?", "Confiar neste certificado?"); put("Trust", "Confiar"); + put("Interrupted", "Interrompido"); + put("The server certificate is for a different address (%1$s).", "O certificado do servidor é doutro enderezo (%1$s)."); + put("The server certificate has expired, or the phone's date is wrong.", "O certificado do servidor caducou ou a data do teléfono é incorrecta."); put("Server certificate changed (does not match the pinned fingerprint)", "O certificado do servidor cambiou (non coincide coa pegada fixada)"); put("Server certificate is not trusted by this phone", "Este teléfono non confía no certificado do servidor"); // errors put("Can't resolve %s. No connection, or a typo in the server address.", "Non se pode resolver %s. Non hai conexión ou hai un erro no enderezo do servidor."); - put("Can't connect to the server (%s). Wrong port, or the bridge is down?", "Non se pode conectar co servidor (%s). Porto incorrecto ou ponte caída?"); + put("Can't connect to the server (%s). Wrong address or port, or the server is down?", + "Non se pode conectar co servidor (%s). Enderezo ou porto incorrecto, ou servidor caído?"); put("The server took too long to answer.", "O servidor tardou de máis en responder."); put("TLS error: %s", "Erro TLS: %s"); put("Network error: %s", "Erro de rede: %s"); @@ -207,12 +211,10 @@ public final class L { "Acceso rexeitado (401). Comproba o usuario e usa un contrasinal de aplicación de Nextcloud (Configuración > Seguranza > Dispositivos e sesións)."); put("Not allowed to write to this calendar (403). It may be read-only/shared.", "Non tes permiso para escribir neste calendario (403). Pode ser de só lectura ou compartido."); put("Too many redirects from the server.", "O servidor fixo demasiadas redireccións."); - put("The server redirected http:// to %s (Nextcloud forces HTTPS / HSTS). Android 2.3 can't follow that: use the legacy-TLS bridge address instead (see README, 'Forced HTTPS / HSTS').", - "O servidor redirixiu http:// a %s (Nextcloud obriga a usar HTTPS / HSTS). Android 2.3 non pode seguir esa redirección: usa o enderezo da ponte TLS antiga (consulta o README, «Forced HTTPS / HSTS»)."); - put("The server redirected to %s, not the address set in the app. Nextcloud is sending its public URL; check the bridge's proxy_redirect line (README, 'Forced HTTPS / HSTS').", - "O servidor redirixiu a %s, que non é o enderezo configurado na aplicación. Nextcloud está a enviar o seu URL público; revisa a liña proxy_redirect da ponte (README, «Forced HTTPS / HSTS»)."); - put("Redirect loop (HTTP %d back to the same address). Nextcloud forces HTTPS but the bridge reaches it over plain HTTP: switch the bridge to proxy_pass https:// (README, 'Forced HTTPS / HSTS').", - "Bucle de redirección (HTTP %d ao mesmo enderezo). Nextcloud obriga a usar HTTPS pero a ponte conecta con el por HTTP: cambia a ponte a proxy_pass https:// (README, «Forced HTTPS / HSTS»)."); + put("The server redirected to %s, not the address set in the app. Put that address in Settings.", + "O servidor redirixiu a %s, que non é o enderezo configurado na aplicación. Pon ese enderezo na configuración."); + put("Redirect loop (HTTP %d back to the same address). Use the server's https:// address in Settings.", + "Bucle de redirección (HTTP %d ao mesmo enderezo). Usa o enderezo https:// do servidor na configuración."); put("%s %s failed: HTTP %d%s", "%s %s fallou: HTTP %d%s"); put(" (not found)", " (non atopado)"); put(" (not a WebDAV URL?)", " (non é un URL WebDAV?)"); @@ -221,8 +223,6 @@ public final class L { put("Saving failed: HTTP %d", "Non se puido gardar: HTTP %d"); put("Event was changed on the server meanwhile. Sync and try again.", "O evento cambiou no servidor mentres tanto. Sincroniza e téntao de novo."); put("Delete failed: HTTP %d", "Non se puido eliminar: HTTP %d"); - put("TLS handshake failed before any certificate was sent. The server most likely requires TLS 1.2+, which Android 2.3 cannot do. Point the app at the legacy-TLS bridge (see README) or use http:// on your LAN. (%s)", - "A negociación TLS fallou antes de recibir ningún certificado. Seguramente o servidor esixe TLS 1.2 ou superior, que Android 2.3 non admite. Configura a aplicación coa ponte TLS antiga (consulta o README) ou usa http:// na rede local. (%s)"); put("Bad XML from server: %s", "XML incorrecto do servidor: %s"); put("Empty XML from server", "XML baleiro do servidor"); put("%d calendars: %d refreshed (%d events)", "Calendarios: %d, actualizados: %d (eventos: %d)"); diff --git a/src/org/gingercal/Tls.java b/src/org/gingercal/Tls.java index 0f455c7..d2d88ac 100644 --- a/src/org/gingercal/Tls.java +++ b/src/org/gingercal/Tls.java @@ -1,20 +1,10 @@ package org.gingercal; -import java.io.IOException; import java.security.GeneralSecurityException; -import java.security.KeyStore; import java.security.MessageDigest; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; import java.util.Locale; -import javax.net.ssl.SSLException; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -/** Certificate pinning for servers the phone can't validate itself (self-signed bridge). */ +/** Certificate fingerprints for pinning servers that aren't CA-signed (self-signed, old bridge). */ public final class Tls { private Tls() {} @@ -38,73 +28,4 @@ public final class Tls { if (s == null) return ""; return s.replaceAll("[^0-9A-Fa-f]", "").toUpperCase(Locale.US); } - - /** Accepts the system CAs, or exactly one pinned leaf certificate (SHA-256). */ - public static class PinTrust implements X509TrustManager { - final String pin; - final X509TrustManager system; - String seenFp, seenSubject, seenIssuer, seenExpiry; - boolean rejected, mismatch; - - public PinTrust(String pin) throws GeneralSecurityException { - this.pin = normPin(pin); - TrustManagerFactory f = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - f.init((KeyStore) null); - X509TrustManager s = null; - TrustManager[] tms = f.getTrustManagers(); - for (int i = 0; i < tms.length; i++) if (tms[i] instanceof X509TrustManager) s = (X509TrustManager) tms[i]; - system = s; - } - - public boolean pinned() { - return pin.length() > 0; - } - - public void reset() { - seenFp = null; - rejected = false; - mismatch = false; - } - - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - throw new CertificateException("client certs not supported"); - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (chain == null || chain.length == 0) throw new CertificateException("empty chain"); - X509Certificate leaf = chain[0]; - seenFp = sha256(leaf.getEncoded()); - seenSubject = leaf.getSubjectDN().getName(); - seenIssuer = leaf.getIssuerDN().getName(); - seenExpiry = new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(leaf.getNotAfter()); - if (pinned()) { - if (normPin(seenFp).equals(pin)) return; - rejected = true; - mismatch = true; - throw new CertificateException("certificate does not match pin"); - } - try { - if (system == null) throw new CertificateException("no system trust store"); - system.checkServerTrusted(chain, authType); - } catch (CertificateException e) { - rejected = true; - throw e; - } - } - - public X509Certificate[] getAcceptedIssuers() { - return system == null ? new X509Certificate[0] : system.getAcceptedIssuers(); - } - - public IOException translate(IOException e) { - if (rejected && seenFp != null) { - return new DavException.UntrustedCert(seenFp, seenSubject, seenIssuer, seenExpiry, mismatch); - } - if (e instanceof SSLException && seenFp == null) { - return new DavException(0, L.f("TLS handshake failed before any certificate was sent. The server most likely requires TLS 1.2+, which Android 2.3 cannot do. Point the app at the legacy-TLS bridge (see README) or use http:// on your LAN. (%s)", e.getMessage())); - } - return e; - } - } - } diff --git a/stubs/android/content/Context.java b/stubs/android/content/Context.java index 2bae2a6..b4cb558 100644 --- a/stubs/android/content/Context.java +++ b/stubs/android/content/Context.java @@ -6,6 +6,7 @@ public abstract class Context { public abstract android.content.SharedPreferences getSharedPreferences(String name, int mode); public abstract android.content.res.Resources getResources(); public abstract Context getApplicationContext(); + public abstract android.content.res.AssetManager getAssets(); public abstract void startActivity(Intent intent); public abstract ComponentName startService(Intent service); public abstract void sendBroadcast(Intent intent); diff --git a/stubs/android/content/ContextWrapper.java b/stubs/android/content/ContextWrapper.java index 8d88556..0756661 100644 --- a/stubs/android/content/ContextWrapper.java +++ b/stubs/android/content/ContextWrapper.java @@ -7,6 +7,7 @@ public class ContextWrapper extends Context { public SharedPreferences getSharedPreferences(String name, int mode) { throw new RuntimeException("Stub!"); } public android.content.res.Resources getResources() { throw new RuntimeException("Stub!"); } public Context getApplicationContext() { throw new RuntimeException("Stub!"); } + public android.content.res.AssetManager getAssets() { throw new RuntimeException("Stub!"); } public void startActivity(Intent intent) { throw new RuntimeException("Stub!"); } public ComponentName startService(Intent service) { throw new RuntimeException("Stub!"); } public void sendBroadcast(Intent intent) { throw new RuntimeException("Stub!"); } diff --git a/stubs/android/content/res/AssetManager.java b/stubs/android/content/res/AssetManager.java new file mode 100644 index 0000000..a83cfb3 --- /dev/null +++ b/stubs/android/content/res/AssetManager.java @@ -0,0 +1,7 @@ +// Compile-only stub (API level 10 signatures). Never packaged into the APK. +package android.content.res; + +public final class AssetManager { + public final String[] list(String path) throws java.io.IOException { throw new RuntimeException("Stub!"); } + public final java.io.InputStream open(String fileName) throws java.io.IOException { throw new RuntimeException("Stub!"); } +} diff --git a/test/org/gingercal/BridgeTest.java b/test/org/gingercal/BridgeTest.java deleted file mode 100644 index b22d19f..0000000 --- a/test/org/gingercal/BridgeTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.gingercal; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.List; -import java.util.UUID; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.TrustManager; - -import org.gingercal.Model.Cal; - -/** Talks to the legacy bridge the way Android 2.3 does: TLS 1.0, AES128-SHA, pinned cert. */ -public class BridgeTest { - static int fails; - - static void check(boolean ok, String what) { - System.out.println((ok ? " ok " : " FAIL ") + what); - if (!ok) fails++; - } - - static class GingerbreadHttp implements Http { - final Tls.PinTrust trust; - final HttpClient c; - - GingerbreadHttp(String pin) throws Exception { - trust = new Tls.PinTrust(pin); - SSLContext ctx = SSLContext.getInstance("TLSv1"); - ctx.init(null, new TrustManager[] {trust}, null); - SSLParameters p = new SSLParameters(new String[] {"TLS_RSA_WITH_AES_128_CBC_SHA"}, new String[] {"TLSv1"}); - c = HttpClient.newBuilder().sslContext(ctx).sslParameters(p).build(); - } - - public Resp exec(String method, String url, String[] h, String body, String ct) throws IOException { - HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(url)).method(method, - body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(body)); - for (int i = 0; i + 1 < h.length; i += 2) b.header(h[i], h[i + 1]); - if (ct != null) b.header("Content-Type", ct); - trust.reset(); - try { - HttpResponse r = c.send(b.build(), HttpResponse.BodyHandlers.ofString()); - Resp o = new Resp(); - o.code = r.statusCode(); - o.body = r.body(); - o.etag = r.headers().firstValue("ETag").orElse(null); - o.location = r.headers().firstValue("Location").orElse(null); - return o; - } catch (IOException e) { - throw trust.translate(e); - } catch (InterruptedException e) { - throw new IOException(e); - } - } - - public void close() {} - } - - public static void main(String[] a) throws Exception { - String url = "https://127.0.0.1:8443"; - String expected = a[0]; - // 1. first contact: not trusted -> app shows fingerprint - try { - new Dav(new GingerbreadHttp(""), url, "xose", "secret").calendarHome(); - check(false, "untrusted cert reported"); - } catch (DavException.UntrustedCert u) { - check(u.fingerprint.equals(expected) && !u.pinMismatch, "untrusted cert reported with fingerprint " + u.fingerprint.substring(0, 11) + "..."); - } - // 2. wrong pin -> mismatch warning - try { - new Dav(new GingerbreadHttp(expected.replace('1', '2')), url, "xose", "secret").calendarHome(); - check(false, "pin mismatch reported"); - } catch (DavException.UntrustedCert u) { - check(u.pinMismatch, "pin mismatch reported"); - } - // 3. pinned: full flow through the bridge - Dav dav = new Dav(new GingerbreadHttp(expected), url, "xose", "secret"); - String home = dav.calendarHome(); - check(home.equals(url + "/remote.php/dav/xose/"), "discovered home via bridge: " + home); - List cals = dav.calendars(home); - check(cals.size() == 2, "calendars via bridge: " + cals.size()); - CoreTest.MemStore st = new CoreTest.MemStore(); - Syncer.Result r = Syncer.sync(dav, st, System.currentTimeMillis(), 1, 60, false); - check(r.events > 0, "sync via bridge: " + r.summary()); - String uid = UUID.randomUUID().toString(); - long s = ICal.startOfDay(System.currentTimeMillis()) + 2 * ICal.DAY + 9 * ICal.HOUR; - Cal w = cals.get(0); - dav.create(w.href, uid, ICal.newEvent(uid, "Via bridge", "", "", s, s + ICal.HOUR, false, 10)); - String[] got = dav.get(w.href + uid + ".ics"); - check(ICal.parse(got[0]).get(0).summary.equals("Via bridge"), "create+get via bridge"); - dav.delete(w.href + uid + ".ics", got[1]); - check(true, "delete via bridge"); - System.out.println(fails == 0 ? "ALL PASSED" : fails + " FAILED"); - System.exit(fails == 0 ? 0 : 1); - } -} diff --git a/test/org/gingercal/I18nTest.java b/test/org/gingercal/I18nTest.java index be997f7..75ee075 100644 --- a/test/org/gingercal/I18nTest.java +++ b/test/org/gingercal/I18nTest.java @@ -42,7 +42,7 @@ public class I18nTest { Dav.checkRedirect("http://192.168.1.20/remote.php/dav/", "https://nxc.anosanube.ovh/remote.php/dav/", new java.util.ArrayList(), 301); } catch (DavException e) { - check(e.getMessage().substring(0, 58), "O servidor redirixiu http:// a https://nxc.anosanube.ovh ("); + check(e.getMessage().substring(0, 44), "O servidor redirixiu a https://nxc.anosanube"); } check(L.t("not in the table"), "not in the table"); // unknown text falls back to English diff --git a/test/org/gingercal/NetTest.java b/test/org/gingercal/NetTest.java new file mode 100644 index 0000000..ef8056e --- /dev/null +++ b/test/org/gingercal/NetTest.java @@ -0,0 +1,55 @@ +package org.gingercal; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.gingercal.net.CertVerifier; + +/** + * The phone's transport (NetHttp: bundled TLS 1.2 client, any method, keep-alive) on a desktop JVM: + * CalDAV discovery against a local Radicale, and certificate checks against real HTTPS servers. + * java -cp out org.gingercal.NetTest assets/roots [http://127.0.0.1:5232 user pass] + */ +public class NetTest { + static int fails; + + static void check(boolean ok, String what) { + System.out.println((ok ? " ok " : " FAIL ") + what); + if (!ok) fails++; + } + + public static void main(String[] a) throws Exception { + final File roots = new File(a[0]); + CertVerifier.setRootStore(new CertVerifier.RootStore() { + public List find(byte[] subject) { + File f = new File(roots, CertVerifier.sha1Hex(subject) + "-0.der"); + try { + return f.exists() ? Collections.singletonList(java.nio.file.Files.readAllBytes(f.toPath())) : null; + } catch (IOException e) { + return null; + } + } + }); + NetHttp h = new NetHttp("", 20000); + Http.Resp r = h.execHere("GET", "https://nxc.anosanube.ovh/status.php", new String[0], null, null); + check(r.code == 200 && r.body.contains("installed"), "Nextcloud over TLS 1.2 with Mozilla roots"); + try { + h.execHere("GET", "https://self-signed.badssl.com/", new String[0], null, null); + check(false, "self-signed refused"); + } catch (DavException.UntrustedCert u) { + check(!u.pinMismatch && u.fingerprint.length() == 95, "self-signed -> trust prompt data"); + Http.Resp p = new NetHttp(u.fingerprint, 20000).execHere("GET", "https://self-signed.badssl.com/", new String[0], null, null); + check(p.code == 200, "pinned self-signed accepted"); + } + if (a.length >= 4) { + Dav d = new Dav(new NetHttp("", 20000), a[1], a[2], a[3]); + String home = d.calendarHome(); + List cals = d.calendars(home); + check(home != null && !cals.isEmpty(), "CalDAV discovery: " + cals.size() + " calendars at " + home); + } + System.out.println(fails == 0 ? "ALL PASSED" : fails + " FAILED"); + System.exit(fails == 0 ? 0 : 1); + } +} diff --git a/test/org/gingercal/RedirectTest.java b/test/org/gingercal/RedirectTest.java index 51c834b..6b8b327 100644 --- a/test/org/gingercal/RedirectTest.java +++ b/test/org/gingercal/RedirectTest.java @@ -57,15 +57,19 @@ public class RedirectTest { } public static void main(String[] a) throws Exception { - // 1. LAN http:// straight at a Nextcloud that forces HTTPS + // 1. LAN http:// address, Nextcloud sends its public https:// one: another server, not followed String m = expectError("http://192.168.1.20", new String[][] {{"301", "https://cloud.example.com/remote.php/dav/"}}); - check(m.startsWith("REDIRECT") && m.contains("forces HTTPS") && m.contains("https://cloud.example.com"), "http->https upgrade explained: " + m.substring(0, 70)); - // 2. bridge passes Nextcloud's absolute public URL through + check(m.startsWith("REDIRECT") && m.contains("Put that address") && m.contains("https://cloud.example.com"), "LAN -> public redirect explained: " + m.substring(0, 70)); + // 1b. http:// -> https:// on the same host is followed now (the app has its own TLS 1.2) + Scripted up = new Scripted(new String[][] {{"301", "https://cloud.example.com/remote.php/dav/"}}); + new Dav(up, "http://cloud.example.com", "u", "p").multistatus("PROPFIND", "http://cloud.example.com/remote.php/dav/", "0", ""); + check(up.urls.size() == 2 && up.urls.get(1).startsWith("https://cloud.example.com/"), "http -> https upgrade followed"); + // 2. another server's absolute URL m = expectError("https://vps.example.com:8443", new String[][] {{"302", "https://cloud.example.com/remote.php/dav/"}}); - check(m.startsWith("REDIRECT") && m.contains("proxy_redirect"), "cross-host redirect explained"); - // 3. bridge with http upstream to a forced-HTTPS Nextcloud: same URL again + check(m.startsWith("REDIRECT") && m.contains("Put that address"), "cross-host redirect explained"); + // 3. the same URL again m = expectError("https://vps.example.com:8443", new String[][] {{"301", "/remote.php/dav/"}, {"301", "/remote.php/dav/"}}); - check(m.startsWith("REDIRECT") && m.contains("loop") && m.contains("proxy_pass https://"), "loop explained after 1 retry"); + check(m.startsWith("REDIRECT") && m.contains("loop"), "loop explained after 1 retry"); // 4. default port written out is the same server Scripted s = new Scripted(new String[][] {{"301", "https://vps.example.com:443/remote.php/dav/"}}); new Dav(s, "https://vps.example.com", "u", "p").multistatus("PROPFIND", "https://vps.example.com/remote.php/dav/", "0", ""); @@ -78,9 +82,9 @@ public class RedirectTest { s = new Scripted(new String[][] {{"301", "https://cloud.example.com/remote.php/dav/"}}); try { new Dav(s, "http://192.168.1.20", "u", "p").calendarHome(); - check(false, "discovery reports forced HTTPS"); + check(false, "discovery reports the redirect"); } catch (DavException e) { - check(e.code == DavException.REDIRECT && s.urls.size() == 1, "discovery reports forced HTTPS immediately"); + check(e.code == DavException.REDIRECT && s.urls.size() == 1, "discovery reports a cross-host redirect immediately"); } System.out.println(fails == 0 ? "ALL PASSED" : fails + " FAILED"); System.exit(fails == 0 ? 0 : 1); diff --git a/tools/check_i18n.py b/tools/check_i18n.py index 542f094..e2b1980 100644 --- a/tools/check_i18n.py +++ b/tools/check_i18n.py @@ -49,6 +49,7 @@ UI_CALL = re.compile(r"\b(setText|setTitle|setMessage|setHint|setPositiveButton| r"new DavException\([^,]+,|new IOException\(|label\(|field\([^,]+,)\s*\(?\s*" + LIT) for d in ("src", "android"): for dirpath, _, files in os.walk(os.path.join(root, d)): + if "/net" in dirpath: continue # bundled TLS client: internal messages, not UI text for f in files: if not f.endswith(".java") or f == "L.java": continue