Backup Server APIs

This commit is contained in:
Fedor Indutny 2024-04-22 16:11:36 +02:00 committed by GitHub
parent 77aea40a63
commit 3eb0e30a23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 991 additions and 201 deletions

View file

@ -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
```
/* ====================================================================

View file

@ -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"
}

View file

@ -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
}

View file

@ -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",

View file

@ -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';

View file

@ -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<void> {
storage: window.storage,
});
backupsService.start();
areWeASubscriberService.update(window.storage, server);
void cleanupSessionResets();

View file

@ -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<void> {
// TODO: DESKTOP-6979
await this.server.refreshBackup(
await this.credentials.getHeadersForToday()
);
}
public async getInfo(): Promise<GetBackupInfoResponseType> {
return this.server.getBackupInfo(
await this.credentials.getHeadersForToday()
);
}
public async getUploadForm(): Promise<GetBackupUploadFormResponseType> {
return this.server.getBackupUploadForm(
await this.credentials.getHeadersForToday()
);
}
public async getMediaUploadForm(): Promise<GetBackupUploadFormResponseType> {
return this.server.getBackupMediaUploadForm(
await this.credentials.getHeadersForToday()
);
}
public async backupMediaBatch(
items: ReadonlyArray<BackupMediaItemType>
): Promise<BackupMediaBatchResponseType> {
return this.server.backupMediaBatch({
headers: await this.credentials.getHeadersForToday(),
items,
});
}
public async listMedia({
cursor,
limit,
}: {
cursor?: string;
limit: number;
}): Promise<BackupListMediaResponseType> {
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;
}
}

View file

@ -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<typeof this.fetch> | undefined;
private readonly fetchBackoff = new BackOff(FIBONACCI_TIMEOUTS);
public start(): void {
this.scheduleFetch();
}
public async getForToday(): Promise<BackupSignedPresentationType> {
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<BackupPresentationHeadersType> {
const { headers } = await this.getForToday();
return headers;
}
public async getCDNCredentials(
cdn: number
): Promise<GetBackupCDNCredentialsResponseType> {
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<void> {
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<ReadonlyArray<BackupCredentialType>> {
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<ReadonlyArray<BackupCredentialType>> {
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<BackupCredentialType>();
const issuedTimes = new Set<number>();
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<void> {
await window.storage.put('backupCredentials', []);
}
}

View file

@ -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);
}

View file

@ -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<void> {
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<void> {
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<void> {
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();

View file

@ -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<keyof typeof URL_CALLS>([
// 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<BackupMediaItemType>;
}>;
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<BackupDeleteMediaItemType>;
}>;
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<MultiRecipient200ResponseType>;
getBackupInfo: (
headers: BackupPresentationHeadersType
) => Promise<GetBackupInfoResponseType>;
getBackupUploadForm: (
headers: BackupPresentationHeadersType
) => Promise<GetBackupUploadFormResponseType>;
getBackupMediaUploadForm: (
headers: BackupPresentationHeadersType
) => Promise<GetBackupUploadFormResponseType>;
refreshBackup: (headers: BackupPresentationHeadersType) => Promise<void>;
getBackupCredentials: (
options: GetBackupCredentialsOptionsType
) => Promise<GetBackupCredentialsResponseType>;
getBackupCDNCredentials: (
options: GetBackupCDNCredentialsOptionsType
) => Promise<GetBackupCDNCredentialsResponseType>;
setBackupId: (options: SetBackupIdOptionsType) => Promise<void>;
setBackupSignatureKey: (
options: SetBackupSignatureKeyOptionsType
) => Promise<void>;
backupMediaBatch: (
options: BackupMediaBatchOptionsType
) => Promise<BackupMediaBatchResponseType>;
backupListMedia: (
options: BackupListMediaOptionsType
) => Promise<BackupListMediaResponseType>;
backupDeleteMedia: (options: BackupDeleteMediaOptionsType) => Promise<void>;
setPhoneNumberDiscoverability: (newValue: boolean) => Promise<void>;
updateDeviceName: (deviceName: string) => Promise<void>;
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<string>();
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',

View file

@ -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<GroupCredentialType>;
callLinkAuthCredentials: ReadonlyArray<GroupCredentialType>;
backupCredentials: ReadonlyArray<BackupCredentialType>;
backupCredentialsLastRequestTime: number;
setBackupSignatureKey: boolean;
lastReceivedAtCounter: number;
preferredReactionEmoji: ReadonlyArray<string>;
skinTone: number;

20
ts/types/backups.ts Normal file
View file

@ -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;
}>;

View file

@ -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"