diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 8a0e806c2ee..b077efe8d35 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -4343,7 +4343,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ``` -## attest 0.1.0, device-transfer 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-core 0.1.0, libsignal-ffi 0.44.0, libsignal-jni 0.44.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, libsignal-node 0.44.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, poksho 0.7.0, signal-crypto 0.1.0, signal-media 0.1.0, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, signal-pin 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0 +## attest 0.1.0, device-transfer 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-core 0.1.0, libsignal-ffi 0.45.0, libsignal-jni 0.45.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, libsignal-node 0.45.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, poksho 0.7.0, signal-crypto 0.1.0, signal-media 0.1.0, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, signal-pin 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0 ``` GNU AFFERO GENERAL PUBLIC LICENSE @@ -5596,7 +5596,7 @@ limitations under the License. ``` -## boring 3.1.0 +## boring 4.6.0 ``` Copyright 2011-2017 Google Inc. @@ -5650,7 +5650,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` -## bindgen 0.66.1 +## bindgen 0.68.1 ``` BSD 3-Clause License @@ -6051,7 +6051,7 @@ express Statement of Purpose. ``` -## boring-sys 3.1.0 +## boring-sys 4.6.0 ``` /* Copyright (c) 2015, Google Inc. @@ -6359,7 +6359,7 @@ DEALINGS IN THE SOFTWARE. ``` -## boring-sys 3.1.0 +## boring-sys 4.6.0 ``` Copyright (c) 2014 Alex Crichton @@ -6599,31 +6599,6 @@ DEALINGS IN THE SOFTWARE. ``` -## hyper 1.2.0 - -``` -Copyright (c) 2014-2021 Sean McArthur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -``` - ## either 1.10.0, itertools 0.11.0, itertools 0.12.1, petgraph 0.6.4 ``` @@ -6944,7 +6919,7 @@ DEALINGS IN THE SOFTWARE. ``` -## boring-sys 3.1.0 +## boring-sys 4.6.0 ``` Copyright (c) 2015-2016 the fiat-crypto authors (see @@ -7301,7 +7276,7 @@ DEALINGS IN THE SOFTWARE. ``` -## lock_api 0.4.11, parking_lot 0.12.1, parking_lot_core 0.9.9, rustc_version 0.4.0 +## rustc_version 0.4.0 ``` Copyright (c) 2016 The Rust Project Developers @@ -7332,6 +7307,38 @@ DEALINGS IN THE SOFTWARE. ``` +## humantime 2.1.0 + +``` +Copyright (c) 2016 The humantime Developers + +Includes parts of http date with the following copyright: +Copyright (c) 2016 Pyfisch + +Includes portions of musl libc with the following copyright: +Copyright © 2005-2013 Rich Felker + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` + ## log-panics 2.1.0 ``` @@ -7357,7 +7364,7 @@ SOFTWARE. ``` -## tokio-boring 3.1.0 +## tokio-boring 4.6.0 ``` Copyright (c) 2016 Tokio contributors @@ -7771,37 +7778,6 @@ SOFTWARE. ``` -## h2 0.4.2 - -``` -Copyright (c) 2017 h2 authors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -``` - ## http 1.1.0 ``` @@ -7833,37 +7809,6 @@ DEALINGS IN THE SOFTWARE. ``` -## signal-hook-registry 1.4.1 - -``` -Copyright (c) 2017 tokio-jsonrpc developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -``` - ## aes 0.8.4 ``` @@ -8080,32 +8025,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -``` - -## want 0.3.1 - -``` -Copyright (c) 2018-2019 Sean McArthur - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ``` ## block-buffer 0.10.4, block-padding 0.3.3 @@ -8200,33 +8119,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -``` - -## try-lock 0.2.5 - -``` -Copyright (c) 2018-2023 Sean McArthur -Copyright (c) 2016 Alex Crichton - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ``` ## opaque-debug 0.3.1 @@ -9058,7 +8950,7 @@ SOFTWARE. ``` -## anstream 0.6.13, anstyle-query 1.0.2, clap 4.4.18, colorchoice 1.0.0, toml_datetime 0.6.5, toml_edit 0.19.15 +## anstream 0.6.13, anstyle-query 1.0.2, clap 4.4.18, colorchoice 1.0.0, env_logger 0.10.2, toml_datetime 0.6.5, toml_edit 0.19.15 ``` Copyright (c) Individual contributors @@ -9788,7 +9680,7 @@ DEALINGS IN THE SOFTWARE. ``` -## adler 1.0.2, anyhow 1.0.80, async-trait 0.1.77, dyn-clone 1.0.17, fastrand 2.0.1, home 0.5.9, itoa 1.0.10, linkme 0.3.25, linkme-impl 0.3.25, linux-raw-sys 0.4.13, minimal-lexical 0.2.1, num_enum 0.6.1, num_enum_derive 0.6.1, once_cell 1.19.0, paste 1.0.14, pin-project-lite 0.2.13, prettyplease 0.2.16, proc-macro-crate 1.3.1, proc-macro2 1.0.78, quote 1.0.35, rustc-hash 1.1.0, rustix 0.38.31, rustversion 1.0.14, semver 1.0.22, send_wrapper 0.6.0, serde 1.0.197, serde_derive 1.0.197, serde_json 1.0.114, syn 1.0.109, syn 2.0.52, syn-mid 0.6.0, thiserror 1.0.57, thiserror-impl 1.0.57, unicode-ident 1.0.12, utf-8 0.7.6 +## adler 1.0.2, anyhow 1.0.80, async-trait 0.1.77, dyn-clone 1.0.17, fastrand 2.0.1, home 0.5.9, is-terminal 0.4.12, itoa 1.0.10, linkme 0.3.25, linkme-impl 0.3.25, linux-raw-sys 0.4.13, minimal-lexical 0.2.1, num_enum 0.6.1, num_enum_derive 0.6.1, once_cell 1.19.0, paste 1.0.14, pin-project-lite 0.2.13, prettyplease 0.2.16, proc-macro-crate 1.3.1, proc-macro2 1.0.78, quote 1.0.35, rustc-hash 1.1.0, rustix 0.38.31, rustversion 1.0.14, semver 1.0.22, send_wrapper 0.6.0, serde 1.0.197, serde_derive 1.0.197, serde_json 1.0.114, syn 1.0.109, syn 2.0.52, syn-mid 0.6.0, thiserror 1.0.57, thiserror-impl 1.0.57, unicode-ident 1.0.12, utf-8 0.7.6 ``` Permission is hereby granted, free of charge, to any @@ -9987,7 +9879,7 @@ THE SOFTWARE. ``` -## aho-corasick 1.1.2, byteorder 1.5.0, memchr 2.7.1, walkdir 2.5.0 +## aho-corasick 1.1.2, byteorder 1.5.0, memchr 2.7.1, termcolor 1.4.1, walkdir 2.5.0 ``` The MIT License (MIT) @@ -10098,7 +9990,7 @@ THE SOFTWARE. ``` -## security-framework 2.9.2, security-framework-sys 2.9.1 +## security-framework 2.10.0, security-framework-sys 2.10.0 ``` The MIT License (MIT) @@ -10480,7 +10372,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -## boring-sys 3.1.0 +## boring-sys 4.6.0 ``` /* ==================================================================== diff --git a/config/default.json b/config/default.json index 4b3709dbee1..2b353cf76b0 100644 --- a/config/default.json +++ b/config/default.json @@ -26,5 +26,5 @@ "certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIIF2zCCA8OgAwIBAgIUAMHz4g60cIDBpPr1gyZ/JDaaPpcwDQYJKoZIhvcNAQEL\nBQAwdTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT\nDU1vdW50YWluIFZpZXcxHjAcBgNVBAoTFVNpZ25hbCBNZXNzZW5nZXIsIExMQzEZ\nMBcGA1UEAxMQU2lnbmFsIE1lc3NlbmdlcjAeFw0yMjAxMjYwMDQ1NTFaFw0zMjAx\nMjQwMDQ1NTBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw\nFAYDVQQHEw1Nb3VudGFpbiBWaWV3MR4wHAYDVQQKExVTaWduYWwgTWVzc2VuZ2Vy\nLCBMTEMxGTAXBgNVBAMTEFNpZ25hbCBNZXNzZW5nZXIwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDEecifxMHHlDhxbERVdErOhGsLO08PUdNkATjZ1kT5\n1uPf5JPiRbus9F4J/GgBQ4ANSAjIDZuFY0WOvG/i0qvxthpW70ocp8IjkiWTNiA8\n1zQNQdCiWbGDU4B1sLi2o4JgJMweSkQFiyDynqWgHpw+KmvytCzRWnvrrptIfE4G\nPxNOsAtXFbVH++8JO42IaKRVlbfpe/lUHbjiYmIpQroZPGPY4Oql8KM3o39ObPnT\no1WoM4moyOOZpU3lV1awftvWBx1sbTBL02sQWfHRxgNVF+Pj0fdDMMFdFJobArrL\nVfK2Ua+dYN4pV5XIxzVarSRW73CXqQ+2qloPW/ynpa3gRtYeGWV4jl7eD0PmeHpK\nOY78idP4H1jfAv0TAVeKpuB5ZFZ2szcySxrQa8d7FIf0kNJe9gIRjbQ+XrvnN+ZZ\nvj6d+8uBJq8LfQaFhlVfI0/aIdggScapR7w8oLpvdflUWqcTLeXVNLVrg15cEDwd\nlV8PVscT/KT0bfNzKI80qBq8LyRmauAqP0CDjayYGb2UAabnhefgmRY6aBE5mXxd\nbyAEzzCS3vDxjeTD8v8nbDq+SD6lJi0i7jgwEfNDhe9XK50baK15Udc8Cr/ZlhGM\njNmWqBd0jIpaZm1rzWA0k4VwXtDwpBXSz8oBFshiXs3FD6jHY2IhOR3ppbyd4qRU\npwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\nHQ4EFgQUtfNLxuXWS9DlgGuMUMNnW7yx83EwHwYDVR0jBBgwFoAUtfNLxuXWS9Dl\ngGuMUMNnW7yx83EwDQYJKoZIhvcNAQELBQADggIBABUeiryS0qjykBN75aoHO9bV\nPrrX+DSJIB9V2YzkFVyh/io65QJMG8naWVGOSpVRwUwhZVKh3JVp/miPgzTGAo7z\nhrDIoXc+ih7orAMb19qol/2Ha8OZLa75LojJNRbZoCR5C+gM8C+spMLjFf9k3JVx\ndajhtRUcR0zYhwsBS7qZ5Me0d6gRXD0ZiSbadMMxSw6KfKk3ePmPb9gX+MRTS63c\n8mLzVYB/3fe/bkpq4RUwzUHvoZf+SUD7NzSQRQQMfvAHlxk11TVNxScYPtxXDyiy\n3Cssl9gWrrWqQ/omuHipoH62J7h8KAYbr6oEIq+Czuenc3eCIBGBBfvCpuFOgckA\nXXE4MlBasEU0MO66GrTCgMt9bAmSw3TrRP12+ZUFxYNtqWluRU8JWQ4FCCPcz9pg\nMRBOgn4lTxDZG+I47OKNuSRjFEP94cdgxd3H/5BK7WHUz1tAGQ4BgepSXgmjzifF\nT5FVTDTl3ZnWUVBXiHYtbOBgLiSIkbqGMCLtrBtFIeQ7RRTb3L+IE9R0UB0cJB3A\nXbf1lVkOcmrdu2h8A32aCwtr5S1fBF1unlG7imPmqJfpOMWa8yIF/KWVm29JAPq8\nLrsybb0z5gg8w7ZblEuB9zOW9M3l60DXuJO6l7g+deV6P96rv2unHS8UlvWiVWDy\n9qfgAJizyy3kqM4lOwBH\n-----END CERTIFICATE-----\n", "serverPublicParams": "ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==", "serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx", - "genericServerPublicParams": "AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N" + "genericServerPublicParams": "AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8" } diff --git a/config/production.json b/config/production.json index e90cc9094e8..480e296ac71 100644 --- a/config/production.json +++ b/config/production.json @@ -14,6 +14,6 @@ "registrationChallengeUrl": "https://signalcaptchas.org/registration/generate.html", "serverPublicParams": "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw==", "serverTrustRoot": "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF", - "genericServerPublicParams": "AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN", + "genericServerPublicParams": "AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O", "updatesEnabled": true } diff --git a/package.json b/package.json index 60dccc1e276..a47403e4b59 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@react-aria/utils": "3.16.0", "@react-spring/web": "9.5.5", "@signalapp/better-sqlite3": "8.7.1", - "@signalapp/libsignal-client": "0.44.0", + "@signalapp/libsignal-client": "0.45.0", "@signalapp/ringrtc": "2.40.0", "@signalapp/windows-dummy-keystroke": "1.0.0", "@types/fabric": "4.5.3", diff --git a/ts/Crypto.ts b/ts/Crypto.ts index a60bfa16511..193e5bd808a 100644 --- a/ts/Crypto.ts +++ b/ts/Crypto.ts @@ -154,6 +154,31 @@ export function deriveBackupKey(masterKey: Uint8Array): Uint8Array { ); } +const BACKUP_SIGNATURE_KEY_LEN = 32; +const BACKUP_SIGNATURE_KEY_INFO = + '20231003_Signal_Backups_GenerateBackupIdKeyPair'; + +export function deriveBackupSignatureKey( + backupKey: Uint8Array, + aciBytes: Uint8Array +): Uint8Array { + if (backupKey.byteLength !== BACKUP_KEY_LEN) { + throw new Error('deriveBackupId: invalid backup key length'); + } + + if (aciBytes.byteLength !== UUID_BYTE_SIZE) { + throw new Error('deriveBackupId: invalid aci length'); + } + + const hkdf = HKDF.new(3); + return hkdf.deriveSecrets( + BACKUP_SIGNATURE_KEY_LEN, + Buffer.from(backupKey), + Buffer.from(BACKUP_SIGNATURE_KEY_INFO), + Buffer.from(aciBytes) + ); +} + const BACKUP_ID_LEN = 16; const BACKUP_ID_INFO = '20231003_Signal_Backups_GenerateBackupId'; diff --git a/ts/background.ts b/ts/background.ts index a855476984b..d6c13bde09b 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -190,6 +190,7 @@ import { getCallLinksForRedux, loadCallLinks, } from './services/callLinksLoader'; +import { backupsService } from './services/backups'; import { getCallIdFromEra, updateLocalGroupCallHistoryTimestamp, @@ -699,6 +700,8 @@ export async function startApp(): Promise { storage: window.storage, }); + backupsService.start(); + areWeASubscriberService.update(window.storage, server); void cleanupSessionResets(); diff --git a/ts/services/backups/api.ts b/ts/services/backups/api.ts new file mode 100644 index 00000000000..ad6cc19de17 --- /dev/null +++ b/ts/services/backups/api.ts @@ -0,0 +1,72 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { strictAssert } from '../../util/assert'; +import type { + WebAPIType, + GetBackupInfoResponseType, + GetBackupUploadFormResponseType, + BackupMediaItemType, + BackupMediaBatchResponseType, + BackupListMediaResponseType, +} from '../../textsecure/WebAPI'; +import type { BackupCredentials } from './credentials'; + +export class BackupAPI { + constructor(private credentials: BackupCredentials) {} + + public async refresh(): Promise { + // TODO: DESKTOP-6979 + await this.server.refreshBackup( + await this.credentials.getHeadersForToday() + ); + } + + public async getInfo(): Promise { + return this.server.getBackupInfo( + await this.credentials.getHeadersForToday() + ); + } + + public async getUploadForm(): Promise { + return this.server.getBackupUploadForm( + await this.credentials.getHeadersForToday() + ); + } + + public async getMediaUploadForm(): Promise { + return this.server.getBackupMediaUploadForm( + await this.credentials.getHeadersForToday() + ); + } + + public async backupMediaBatch( + items: ReadonlyArray + ): Promise { + return this.server.backupMediaBatch({ + headers: await this.credentials.getHeadersForToday(), + items, + }); + } + + public async listMedia({ + cursor, + limit, + }: { + cursor?: string; + limit: number; + }): Promise { + return this.server.backupListMedia({ + headers: await this.credentials.getHeadersForToday(), + cursor, + limit, + }); + } + + private get server(): WebAPIType { + const { server } = window.textsecure; + strictAssert(server, 'server not available'); + + return server; + } +} diff --git a/ts/services/backups/credentials.ts b/ts/services/backups/credentials.ts new file mode 100644 index 00000000000..8b4a913fb5f --- /dev/null +++ b/ts/services/backups/credentials.ts @@ -0,0 +1,288 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { PrivateKey } from '@signalapp/libsignal-client'; +import { + BackupAuthCredential, + BackupAuthCredentialRequestContext, + BackupAuthCredentialResponse, + GenericServerPublicParams, +} from '@signalapp/libsignal-client/zkgroup'; + +import * as log from '../../logging/log'; +import { strictAssert } from '../../util/assert'; +import { drop } from '../../util/drop'; +import { toDayMillis } from '../../util/timestamp'; +import { DAY, DurationInSeconds } from '../../util/durations'; +import { BackOff, FIBONACCI_TIMEOUTS } from '../../util/BackOff'; +import type { + BackupCredentialType, + BackupPresentationHeadersType, + BackupSignedPresentationType, +} from '../../types/backups'; +import { toLogFormat } from '../../types/errors'; +import { HTTPError } from '../../textsecure/Errors'; +import type { + GetBackupCredentialsResponseType, + GetBackupCDNCredentialsResponseType, +} from '../../textsecure/WebAPI'; +import { getBackupKey, getBackupSignatureKey } from './crypto'; + +export function getAuthContext(): BackupAuthCredentialRequestContext { + return BackupAuthCredentialRequestContext.create( + Buffer.from(getBackupKey()), + window.storage.user.getCheckedAci() + ); +} + +const FETCH_INTERVAL = 3 * DAY; + +export class BackupCredentials { + private activeFetch: ReturnType | undefined; + + private readonly fetchBackoff = new BackOff(FIBONACCI_TIMEOUTS); + + public start(): void { + this.scheduleFetch(); + } + + public async getForToday(): Promise { + const now = toDayMillis(Date.now()); + + const signatureKeyBytes = getBackupSignatureKey(); + const signatureKey = PrivateKey.deserialize(Buffer.from(signatureKeyBytes)); + + // Start with cache + let credentials = window.storage.get('backupCredentials') || []; + + let result = credentials.find(({ redemptionTimeMs }) => { + return redemptionTimeMs === now; + }); + + if (result === undefined) { + log.info(`BackupCredentials: cache miss for ${now}`); + credentials = await this.fetch(); + result = credentials.find(({ redemptionTimeMs }) => { + return redemptionTimeMs === now; + }); + strictAssert( + result !== undefined, + 'Remote credentials do not include today' + ); + } + + const cred = new BackupAuthCredential( + Buffer.from(result.credential, 'base64') + ); + + const serverPublicParams = new GenericServerPublicParams( + Buffer.from(window.getGenericServerPublicParams(), 'base64') + ); + + const presentation = cred.present(serverPublicParams).serialize(); + const signature = signatureKey.sign(presentation); + + const headers = { + 'X-Signal-ZK-Auth': presentation.toString('base64'), + 'X-Signal-ZK-Auth-Signature': signature.toString('base64'), + }; + + if (!window.storage.get('setBackupSignatureKey')) { + log.warn('BackupCredentials: uploading signature key'); + + const { server } = window.textsecure; + strictAssert(server, 'server not available'); + + await server.setBackupSignatureKey({ + headers, + backupIdPublicKey: signatureKey.getPublicKey().serialize(), + }); + + await window.storage.put('setBackupSignatureKey', true); + } + + return { + headers, + level: result.level, + }; + } + + public async getHeadersForToday(): Promise { + const { headers } = await this.getForToday(); + return headers; + } + + public async getCDNCredentials( + cdn: number + ): Promise { + const { server } = window.textsecure; + strictAssert(server, 'server not available'); + + const headers = await this.getHeadersForToday(); + + return server.getBackupCDNCredentials({ headers, cdn }); + } + + private scheduleFetch(): void { + const lastFetchAt = window.storage.get( + 'backupCredentialsLastRequestTime', + 0 + ); + const nextFetchAt = lastFetchAt + FETCH_INTERVAL; + const delay = Math.max(0, nextFetchAt - Date.now()); + + log.info(`BackupCredentials: scheduling fetch in ${delay}ms`); + setTimeout(() => drop(this.runPeriodicFetch()), delay); + } + + private async runPeriodicFetch(): Promise { + try { + log.info('BackupCredentials: fetching'); + await this.fetch(); + + await window.storage.put('backupCredentialsLastRequestTime', Date.now()); + + this.fetchBackoff.reset(); + this.scheduleFetch(); + } catch (error) { + const delay = this.fetchBackoff.get(); + log.error( + 'BackupCredentials: periodic fetch failed with ' + + `error: ${toLogFormat(error)}, retrying in ${delay}ms` + ); + setTimeout(() => this.scheduleFetch(), delay); + } + } + + private async fetch(): Promise> { + if (this.activeFetch) { + return this.activeFetch; + } + + const promise = this.doFetch(); + this.activeFetch = promise; + + try { + return await promise; + } finally { + this.activeFetch = undefined; + } + } + + private async doFetch(): Promise> { + log.info('BackupCredentials: fetching'); + + const now = Date.now(); + const startDayInMs = toDayMillis(now); + const endDayInMs = now + 6 * DAY; + + // And fetch missing credentials + const ctx = getAuthContext(); + const { server } = window.textsecure; + strictAssert(server, 'server not available'); + + let response: GetBackupCredentialsResponseType; + try { + response = await server.getBackupCredentials({ + startDayInMs, + endDayInMs, + }); + } catch (error) { + if (!(error instanceof HTTPError)) { + throw error; + } + + if (error.code !== 404) { + throw error; + } + + // Backup id is missing + const request = ctx.getRequest(); + + // Set it + await server.setBackupId({ + backupAuthCredentialRequest: request.serialize(), + }); + + // And try again! + response = await server.getBackupCredentials({ + startDayInMs, + endDayInMs, + }); + } + + log.info(`BackupCredentials: got ${response.credentials.length}`); + + const serverPublicParams = new GenericServerPublicParams( + Buffer.from(window.getGenericServerPublicParams(), 'base64') + ); + + const result = new Array(); + + const issuedTimes = new Set(); + for (const { credential: buf, redemptionTime } of response.credentials) { + const credentialRes = new BackupAuthCredentialResponse(Buffer.from(buf)); + + const redemptionTimeMs = DurationInSeconds.toMillis(redemptionTime); + strictAssert( + startDayInMs <= redemptionTimeMs, + 'Invalid credential response redemption time, too early' + ); + strictAssert( + redemptionTimeMs <= endDayInMs, + 'Invalid credential response redemption time, too late' + ); + + strictAssert( + !issuedTimes.has(redemptionTimeMs), + 'Invalid credential response redemption time, duplicate' + ); + issuedTimes.add(redemptionTimeMs); + + const credential = ctx.receive( + credentialRes, + redemptionTime, + serverPublicParams + ); + + result.push({ + credential: credential.serialize().toString('base64'), + level: credential.getBackupLevel(), + redemptionTimeMs, + }); + } + + // Add cached credentials that are still in the date range, and not in + // the response. + const cachedCredentials = window.storage.get('backupCredentials') || []; + for (const cached of cachedCredentials) { + const { redemptionTimeMs } = cached; + if ( + !(startDayInMs <= redemptionTimeMs && redemptionTimeMs <= endDayInMs) + ) { + continue; + } + + if (issuedTimes.has(redemptionTimeMs)) { + continue; + } + result.push(cached); + } + + result.sort((a, b) => a.redemptionTimeMs - b.redemptionTimeMs); + await window.storage.put('backupCredentials', result); + + const startMs = result[0].redemptionTimeMs; + const endMs = result[result.length - 1].redemptionTimeMs; + log.info(`BackupCredentials: saved [${startMs}, ${endMs}]`); + + strictAssert(result.length === 7, 'Expected one week of credentials'); + + return result; + } + + // Called when backup tier changes + public async clear(): Promise { + await window.storage.put('backupCredentials', []); + } +} diff --git a/ts/services/backups/crypto.ts b/ts/services/backups/crypto.ts new file mode 100644 index 00000000000..c1fdcb71d2d --- /dev/null +++ b/ts/services/backups/crypto.ts @@ -0,0 +1,53 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import memoizee from 'memoizee'; + +import { strictAssert } from '../../util/assert'; +import type { AciString } from '../../types/ServiceId'; +import { toAciObject } from '../../util/ServiceId'; +import { + deriveBackupKey, + deriveBackupSignatureKey, + deriveBackupId, + deriveBackupKeyMaterial, +} from '../../Crypto'; +import type { BackupKeyMaterialType } from '../../Crypto'; + +const getMemoizedBackupKey = memoizee((masterKey: string) => { + return deriveBackupKey(Buffer.from(masterKey, 'base64')); +}); + +export function getBackupKey(): Uint8Array { + const masterKey = window.storage.get('masterKey'); + strictAssert(masterKey, 'Master key not available'); + + return getMemoizedBackupKey(masterKey); +} + +const getMemoizedBackupSignatureKey = memoizee( + (backupKey: Uint8Array, aci: AciString) => { + const aciBytes = toAciObject(aci).getServiceIdBinary(); + return deriveBackupSignatureKey(backupKey, aciBytes); + } +); + +export function getBackupSignatureKey(): Uint8Array { + const backupKey = getBackupKey(); + const aci = window.storage.user.getCheckedAci(); + return getMemoizedBackupSignatureKey(backupKey, aci); +} + +const getMemoizedKeyMaterial = memoizee( + (backupKey: Uint8Array, aci: AciString) => { + const aciBytes = toAciObject(aci).getServiceIdBinary(); + const backupId = deriveBackupId(backupKey, aciBytes); + return deriveBackupKeyMaterial(backupKey, backupId); + } +); + +export function getKeyMaterial(): BackupKeyMaterialType { + const backupKey = getBackupKey(); + const aci = window.storage.user.getCheckedAci(); + return getMemoizedKeyMaterial(backupKey, aci); +} diff --git a/ts/services/backups/index.ts b/ts/services/backups/index.ts index f6674a5a03b..911616291bd 100644 --- a/ts/services/backups/index.ts +++ b/ts/services/backups/index.ts @@ -11,47 +11,48 @@ import { noop } from 'lodash'; import * as log from '../../logging/log'; import * as Bytes from '../../Bytes'; +import { strictAssert } from '../../util/assert'; +import { drop } from '../../util/drop'; import { DelimitedStream } from '../../util/DelimitedStream'; import { appendPaddingStream } from '../../util/logPadding'; import { prependStream } from '../../util/prependStream'; import { appendMacStream } from '../../util/appendMacStream'; -import { toAciObject } from '../../util/ServiceId'; +import { HOUR } from '../../util/durations'; import { CipherType, HashType } from '../../types/Crypto'; import * as Errors from '../../types/errors'; -import { - deriveBackupKey, - deriveBackupId, - deriveBackupKeyMaterial, - constantTimeEqual, -} from '../../Crypto'; -import type { BackupKeyMaterialType } from '../../Crypto'; +import { constantTimeEqual } from '../../Crypto'; import { getIvAndDecipher, getMacAndUpdateHmac } from '../../AttachmentCrypto'; import { BackupExportStream } from './export'; import { BackupImportStream } from './import'; +import { getKeyMaterial } from './crypto'; +import { BackupCredentials } from './credentials'; +import { BackupAPI } from './api'; const IV_LENGTH = 16; -function getKeyMaterial(): BackupKeyMaterialType { - const masterKey = window.storage.get('masterKey'); - if (!masterKey) { - throw new Error('Master key not available'); - } - - const aci = toAciObject(window.storage.user.getCheckedAci()); - const aciBytes = aci.getServiceIdBinary(); - - const backupKey = deriveBackupKey(Bytes.fromBase64(masterKey)); - const backupId = deriveBackupId(backupKey, aciBytes); - return deriveBackupKeyMaterial(backupKey, backupId); -} +const BACKUP_REFRESH_INTERVAL = 24 * HOUR; export class BackupsService { + private isStarted = false; private isRunning = false; + public readonly credentials = new BackupCredentials(); + public readonly api = new BackupAPI(this.credentials); + + public start(): void { + strictAssert(!this.isStarted, 'Already started'); + this.isStarted = true; + + setInterval(() => { + drop(this.runPeriodicRefresh()); + }, BACKUP_REFRESH_INTERVAL); + + drop(this.runPeriodicRefresh()); + this.credentials.start(); + } + public async exportBackup(sink: Writable): Promise { - if (this.isRunning) { - throw new Error('BackupService is already running'); - } + strictAssert(!this.isRunning, 'BackupService is already running'); log.info('exportBackup: starting...'); this.isRunning = true; @@ -108,9 +109,7 @@ export class BackupsService { } public async importBackup(createBackupStream: () => Readable): Promise { - if (this.isRunning) { - throw new Error('BackupService is already running'); - } + strictAssert(!this.isRunning, 'BackupService is already running'); log.info('importBackup: starting...'); this.isRunning = true; @@ -134,13 +133,11 @@ export class BackupsService { sink ); - if (theirMac == null) { - throw new Error('importBackup: Missing MAC'); - } - - if (!constantTimeEqual(hmac.digest(), theirMac)) { - throw new Error('importBackup: Bad MAC'); - } + strictAssert(theirMac != null, 'importBackup: Missing MAC'); + strictAssert( + constantTimeEqual(hmac.digest(), theirMac), + 'importBackup: Bad MAC' + ); // Second pass - decrypt (but still check the mac at the end) hmac = createHmac(HashType.size256, macKey); @@ -154,9 +151,10 @@ export class BackupsService { new BackupImportStream() ); - if (!constantTimeEqual(hmac.digest(), theirMac)) { - throw new Error('importBackup: Bad MAC, second pass'); - } + strictAssert( + constantTimeEqual(hmac.digest(), theirMac), + 'importBackup: Bad MAC, second pass' + ); log.info('importBackup: finished...'); } catch (error) { @@ -166,6 +164,15 @@ export class BackupsService { this.isRunning = false; } } + + private async runPeriodicRefresh(): Promise { + try { + await this.api.refresh(); + log.info('Backup: refreshed'); + } catch (error) { + log.error('Backup: periodic refresh failed', Errors.toLogFormat(error)); + } + } } export const backupsService = new BackupsService(); diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index ae802ddc71f..31f52e1a952 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -49,6 +49,7 @@ import { untaggedPniSchema, } from '../types/ServiceId'; import type { DirectoryConfigType } from '../types/RendererConfig'; +import type { BackupPresentationHeadersType } from '../types/backups'; import * as Bytes from '../Bytes'; import { randomInt } from '../Crypto'; import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch'; @@ -536,6 +537,10 @@ const URL_CALLS = { 'dynamic/desktop/stories/onboarding/manifest.json', getStickerPackUpload: 'v1/sticker/pack/form', getArtAuth: 'v1/art/auth', + getBackupCredentials: 'v1/archives/auth', + getBackupCDNCredentials: 'v1/archives/auth/read', + getBackupUploadForm: 'v1/archives/upload/form', + getBackupMediaUploadForm: 'v1/archives/media/upload/form', groupLog: 'v1/groups/logs', groupJoinedAtVersion: 'v1/groups/joined_at_version', groups: 'v1/groups', @@ -547,9 +552,15 @@ const URL_CALLS = { multiRecipient: 'v1/messages/multi_recipient', phoneNumberDiscoverability: 'v2/accounts/phone_number_discoverability', profile: 'v1/profile', + backup: 'v1/archives', + backupMedia: 'v1/archives/media', + backupMediaBatch: 'v1/archives/media/batch', + backupMediaDelete: 'v1/archives/media/delete', registration: 'v1/registration', registerCapabilities: 'v1/devices/capabilities', reportMessage: 'v1/messages/report', + setBackupId: 'v1/archives/backupid', + setBackupSignatureKey: 'v1/archives/keys', signed: 'v2/keys/signed', storageManifest: 'v1/storage/manifest', storageModify: 'v1/storage/', @@ -599,6 +610,18 @@ const WEBSOCKET_CALLS = new Set([ // Account V2 'phoneNumberDiscoverability', + + // Backups + 'getBackupCredentials', + 'getBackupCDNCredentials', + 'getBackupMediaUploadForm', + 'getBackupUploadForm', + 'backup', + 'backupMedia', + 'backupMediaBatch', + 'backupMediaDelete', + 'setBackupId', + 'setBackupSignatureKey', ]); type InitializeOptionsType = { @@ -982,6 +1005,136 @@ export type RequestVerificationResultType = Readonly<{ sessionId: string; }>; +export type SetBackupIdOptionsType = Readonly<{ + backupAuthCredentialRequest: Uint8Array; +}>; + +export type SetBackupSignatureKeyOptionsType = Readonly<{ + headers: BackupPresentationHeadersType; + backupIdPublicKey: Uint8Array; +}>; + +export type BackupMediaItemType = Readonly<{ + sourceAttachment: Readonly<{ + cdn: number; + key: string; + }>; + objectLength: number; + mediaId: string; + hmacKey: Uint8Array; + encryptionKey: Uint8Array; + iv: Uint8Array; +}>; + +export type BackupMediaBatchOptionsType = Readonly<{ + headers: BackupPresentationHeadersType; + items: ReadonlyArray; +}>; + +export const backupMediaBatchResponseSchema = z.object({ + responses: z + .object({ + status: z.number(), + failureReason: z.string().or(z.null()).optional(), + cdn: z.number(), + mediaId: z.string(), + }) + .array(), +}); + +export type BackupMediaBatchResponseType = z.infer< + typeof backupMediaBatchResponseSchema +>; + +export type BackupListMediaOptionsType = Readonly<{ + headers: BackupPresentationHeadersType; + cursor?: string; + limit: number; +}>; + +export const backupListMediaResponseSchema = z.object({ + storedMediaObjects: z + .object({ + cdn: z.number(), + mediaId: z.string(), + objectLength: z.number(), + }) + .array(), + backupDir: z.string(), + mediaDir: z.string(), + cursor: z.string().or(z.null()).optional(), +}); + +export type BackupListMediaResponseType = z.infer< + typeof backupListMediaResponseSchema +>; + +export type BackupDeleteMediaItemType = Readonly<{ + cdn: number; + mediaId: string; +}>; + +export type BackupDeleteMediaOptionsType = Readonly<{ + headers: BackupPresentationHeadersType; + mediaToDelete: ReadonlyArray; +}>; + +export type GetBackupCredentialsOptionsType = Readonly<{ + startDayInMs: number; + endDayInMs: number; +}>; + +export const getBackupCredentialsResponseSchema = z.object({ + credentials: z + .object({ + credential: z.string().transform(x => Bytes.fromBase64(x)), + redemptionTime: z + .number() + .transform(x => durations.DurationInSeconds.fromSeconds(x)), + }) + .array(), +}); + +export type GetBackupCredentialsResponseType = z.infer< + typeof getBackupCredentialsResponseSchema +>; + +export type GetBackupCDNCredentialsOptionsType = Readonly<{ + headers: BackupPresentationHeadersType; + cdn: number; +}>; + +export const getBackupCDNCredentialsResponseSchema = z.object({ + headers: z.record(z.string(), z.string()), +}); + +export type GetBackupCDNCredentialsResponseType = z.infer< + typeof getBackupCDNCredentialsResponseSchema +>; + +export const getBackupInfoResponseSchema = z.object({ + cdn: z.number(), + backupDir: z.string(), + mediaDir: z.string(), + backupName: z.string(), + usedSpace: z.number().or(z.null()).optional(), +}); + +export type GetBackupInfoResponseType = z.infer< + typeof getBackupInfoResponseSchema +>; + +export const getBackupUploadFormResponseSchema = z.object({ + cdn: z.number(), + key: z.string(), + headers: z.record(z.string(), z.string()), + signedUploadLocation: z.string(), +}); + +export type GetBackupUploadFormResponseType = z.infer< + typeof getBackupUploadFormResponseSchema +>; + export type WebAPIType = { startRegistration(): unknown; finishRegistration(baton: unknown): void; @@ -1166,6 +1319,33 @@ export type WebAPIType = { urgent?: boolean; } ) => Promise; + getBackupInfo: ( + headers: BackupPresentationHeadersType + ) => Promise; + getBackupUploadForm: ( + headers: BackupPresentationHeadersType + ) => Promise; + getBackupMediaUploadForm: ( + headers: BackupPresentationHeadersType + ) => Promise; + refreshBackup: (headers: BackupPresentationHeadersType) => Promise; + getBackupCredentials: ( + options: GetBackupCredentialsOptionsType + ) => Promise; + getBackupCDNCredentials: ( + options: GetBackupCDNCredentialsOptionsType + ) => Promise; + setBackupId: (options: SetBackupIdOptionsType) => Promise; + setBackupSignatureKey: ( + options: SetBackupSignatureKeyOptionsType + ) => Promise; + backupMediaBatch: ( + options: BackupMediaBatchOptionsType + ) => Promise; + backupListMedia: ( + options: BackupListMediaOptionsType + ) => Promise; + backupDeleteMedia: (options: BackupDeleteMediaOptionsType) => Promise; setPhoneNumberDiscoverability: (newValue: boolean) => Promise; updateDeviceName: (deviceName: string) => Promise; uploadAvatar: ( @@ -1447,6 +1627,9 @@ export function initialize({ // Thanks, function hoisting! return { authenticate, + backupDeleteMedia, + backupListMedia, + backupMediaBatch, cancelInflightRequests, cdsLookup, checkAccountExistence, @@ -1466,6 +1649,11 @@ export function initialize({ getAttachment, getAttachmentV2, getAvatar, + getBackupCredentials, + getBackupCDNCredentials, + getBackupInfo, + getBackupMediaUploadForm, + getBackupUploadForm, getBadgeImageFile, getConfig, getGroup, @@ -1507,6 +1695,7 @@ export function initialize({ putProfile, putStickers, reconnect, + refreshBackup, registerCapabilities, registerKeys, registerRequestHandler, @@ -1520,6 +1709,8 @@ export function initialize({ sendMessages, sendMessagesUnauth, sendWithSenderKey, + setBackupId, + setBackupSignatureKey, setPhoneNumberDiscoverability, startRegistration, unregisterRequestHandler, @@ -2497,6 +2688,208 @@ export function initialize({ }); } + async function getBackupInfo(headers: BackupPresentationHeadersType) { + const res = await _ajax({ + call: 'backup', + httpType: 'GET', + unauthenticated: true, + accessKey: undefined, + headers, + responseType: 'json', + }); + + return getBackupInfoResponseSchema.parse(res); + } + + async function getBackupMediaUploadForm( + headers: BackupPresentationHeadersType + ) { + const res = await _ajax({ + call: 'getBackupMediaUploadForm', + httpType: 'GET', + unauthenticated: true, + accessKey: undefined, + headers, + responseType: 'json', + }); + + return getBackupUploadFormResponseSchema.parse(res); + } + + async function getBackupUploadForm(headers: BackupPresentationHeadersType) { + const res = await _ajax({ + call: 'getBackupUploadForm', + httpType: 'GET', + unauthenticated: true, + accessKey: undefined, + headers, + responseType: 'json', + }); + + return getBackupUploadFormResponseSchema.parse(res); + } + + async function refreshBackup(headers: BackupPresentationHeadersType) { + await _ajax({ + call: 'backup', + httpType: 'POST', + unauthenticated: true, + accessKey: undefined, + headers, + }); + } + + async function getBackupCredentials({ + startDayInMs, + endDayInMs, + }: GetBackupCredentialsOptionsType) { + const startDayInSeconds = startDayInMs / durations.SECOND; + const endDayInSeconds = endDayInMs / durations.SECOND; + const res = await _ajax({ + call: 'getBackupCredentials', + httpType: 'GET', + urlParameters: + `?redemptionStartSeconds=${startDayInSeconds}&` + + `redemptionEndSeconds=${endDayInSeconds}`, + responseType: 'json', + }); + + return getBackupCredentialsResponseSchema.parse(res); + } + + async function getBackupCDNCredentials({ + headers, + cdn, + }: GetBackupCDNCredentialsOptionsType) { + const res = await _ajax({ + call: 'getBackupCDNCredentials', + httpType: 'GET', + unauthenticated: true, + accessKey: undefined, + headers, + urlParameters: `?cdn=${cdn}`, + responseType: 'json', + }); + + return getBackupCDNCredentialsResponseSchema.parse(res); + } + + async function setBackupId({ + backupAuthCredentialRequest, + }: SetBackupIdOptionsType) { + await _ajax({ + call: 'setBackupId', + httpType: 'PUT', + jsonData: { + backupAuthCredentialRequest: Bytes.toBase64( + backupAuthCredentialRequest + ), + }, + }); + } + + async function setBackupSignatureKey({ + headers, + backupIdPublicKey, + }: SetBackupSignatureKeyOptionsType) { + await _ajax({ + call: 'setBackupSignatureKey', + httpType: 'PUT', + unauthenticated: true, + accessKey: undefined, + headers, + jsonData: { + backupIdPublicKey: Bytes.toBase64(backupIdPublicKey), + }, + }); + } + + async function backupMediaBatch({ + headers, + items, + }: BackupMediaBatchOptionsType) { + const res = await _ajax({ + call: 'backupMediaBatch', + httpType: 'PUT', + unauthenticated: true, + accessKey: undefined, + headers, + responseType: 'json', + jsonData: { + items: items.map(item => { + const { + sourceAttachment, + objectLength, + mediaId, + hmacKey, + encryptionKey, + iv, + } = item; + + return { + sourceAttachment: { + cdn: sourceAttachment.cdn, + key: sourceAttachment.key, + }, + objectLength, + mediaId, + hmacKey: Bytes.toBase64(hmacKey), + encryptionKey: Bytes.toBase64(encryptionKey), + iv: Bytes.toBase64(iv), + }; + }), + }, + }); + + return backupMediaBatchResponseSchema.parse(res); + } + + async function backupDeleteMedia({ + headers, + mediaToDelete, + }: BackupDeleteMediaOptionsType) { + await _ajax({ + call: 'backupMediaDelete', + httpType: 'POST', + unauthenticated: true, + accessKey: undefined, + headers, + jsonData: { + mediaToDelete: mediaToDelete.map(({ cdn, mediaId }) => { + return { + cdn, + mediaId, + }; + }), + }, + }); + } + + async function backupListMedia({ + headers, + cursor, + limit, + }: BackupListMediaOptionsType) { + const params = new Array(); + + if (cursor != null) { + params.push(`cursor=${encodeURIComponent(cursor)}`); + } + params.push(`limit=${limit}`); + + const res = await _ajax({ + call: 'backupMedia', + httpType: 'GET', + unauthenticated: true, + accessKey: undefined, + headers, + responseType: 'json', + urlParameters: `?${params.join('&')}`, + }); + + return backupListMediaResponseSchema.parse(res); + } + async function setPhoneNumberDiscoverability(newValue: boolean) { await _ajax({ call: 'phoneNumberDiscoverability', diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index 8cd4c9c84ce..9e37dc5e032 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -18,6 +18,7 @@ import type { SessionResetsType, StorageServiceCredentials, } from '../textsecure/Types.d'; +import type { BackupCredentialType } from './backups'; import type { ServiceIdString } from './ServiceId'; import type { RegisteredChallengeType } from '../challenge'; @@ -134,6 +135,9 @@ export type StorageAccessType = { unidentifiedDeliveryIndicators: boolean; groupCredentials: ReadonlyArray; callLinkAuthCredentials: ReadonlyArray; + backupCredentials: ReadonlyArray; + backupCredentialsLastRequestTime: number; + setBackupSignatureKey: boolean; lastReceivedAtCounter: number; preferredReactionEmoji: ReadonlyArray; skinTone: number; diff --git a/ts/types/backups.ts b/ts/types/backups.ts new file mode 100644 index 00000000000..ddb2e36e28f --- /dev/null +++ b/ts/types/backups.ts @@ -0,0 +1,20 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { BackupLevel } from '@signalapp/libsignal-client/zkgroup'; + +export type BackupCredentialType = Readonly<{ + credential: string; + level: BackupLevel; + redemptionTimeMs: number; +}>; + +export type BackupPresentationHeadersType = Readonly<{ + 'X-Signal-ZK-Auth': string; + 'X-Signal-ZK-Auth-Signature': string; +}>; + +export type BackupSignedPresentationType = Readonly<{ + headers: BackupPresentationHeadersType; + level: BackupLevel; +}>; diff --git a/yarn.lock b/yarn.lock index 8a85385c3fa..ae22b5e1de8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3983,10 +3983,10 @@ bindings "^1.5.0" tar "^6.1.0" -"@signalapp/libsignal-client@0.44.0": - version "0.44.0" - resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.44.0.tgz#c08bf33bb16276baff35a932b1f43d19d17028cf" - integrity sha512-tbxkRoNd1l2cg6aN5tyiTCy724qU/OlKpX9IECbMu/W1V77SaQS6ch0kPdR+KClBMyEtWxnd/q3/V4NMiENF4w== +"@signalapp/libsignal-client@0.45.0": + version "0.45.0" + resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.45.0.tgz#c6b9125bd7bded0fd349484df264ee2763f8b714" + integrity sha512-jCP8NHeqVLECjfboTNJJGLtTiY6BLcAg0W9/OUkgFNXLPjKIig9RaRWtYIq5DjcN4Zis5o16X4v+DKMMLOy5Sw== dependencies: node-gyp-build "^4.2.3" type-fest "^3.5.0" @@ -7577,7 +7577,7 @@ caniuse-lite@^1.0.30001349, caniuse-lite@^1.0.30001541: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz" integrity sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw== -canvas@^2.6.1, "canvas@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz", dmg-license@^1.0.11, "dmg-license@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz", jsdom@^15.2.1, "jsdom@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz": +canvas@^2.6.1, "canvas@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz": version "1.0.0" resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca" @@ -8955,6 +8955,10 @@ dmg-builder@24.6.3: optionalDependencies: dmg-license "^1.0.11" +dmg-license@^1.0.11, "dmg-license@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz": + version "1.0.0" + resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -13383,6 +13387,10 @@ jsdoc@^4.0.0: strip-json-comments "^3.1.0" underscore "~1.13.2" +jsdom@^15.2.1, "jsdom@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz": + version "1.0.0" + resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -18446,7 +18454,7 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18489,6 +18497,15 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -18569,7 +18586,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18603,6 +18620,13 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -20220,7 +20244,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -20254,6 +20278,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"