Backup Server APIs
This commit is contained in:
parent
77aea40a63
commit
3eb0e30a23
14 changed files with 991 additions and 201 deletions
|
@ -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
|
||||
|
||||
```
|
||||
/* ====================================================================
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
25
ts/Crypto.ts
25
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';
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
72
ts/services/backups/api.ts
Normal file
72
ts/services/backups/api.ts
Normal 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;
|
||||
}
|
||||
}
|
288
ts/services/backups/credentials.ts
Normal file
288
ts/services/backups/credentials.ts
Normal 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', []);
|
||||
}
|
||||
}
|
53
ts/services/backups/crypto.ts
Normal file
53
ts/services/backups/crypto.ts
Normal 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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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',
|
||||
|
|
4
ts/types/Storage.d.ts
vendored
4
ts/types/Storage.d.ts
vendored
|
@ -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
20
ts/types/backups.ts
Normal 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;
|
||||
}>;
|
49
yarn.lock
49
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"
|
||||
|
|
Loading…
Reference in a new issue