diff --git a/Cargo.lock b/Cargo.lock index b9a74e5ff..80ae3098e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,23 +46,23 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +checksum = "0fa882656b67966045e4152c634051e70346939fced7117d5f0b52146a7c74c9" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", - "ahash", "base64 0.22.1", "bitflags 2.9.0", - "brotli 6.0.0", + "brotli", "bytes", "bytestring", - "derive_more", + "derive_more 2.0.1", "encoding_rs", "flate2", + "foldhash", "futures-core", "h2 0.3.26", "http 0.2.12", @@ -74,7 +74,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.9.0", "sha1", "smallvec", "tokio", @@ -90,7 +90,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +checksum = "6398974fd4284f4768af07965701efbbb5fdc0616bff20cade1bb14b77675e24" dependencies = [ "actix-rt", "actix-service", @@ -138,12 +138,11 @@ dependencies = [ [[package]] name = "actix-service" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" dependencies = [ "futures-core", - "paste", "pin-project-lite", ] @@ -159,9 +158,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.9.0" +version = "4.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +checksum = "f2e3b15b3dc6c6ed996e4032389e9849d4ab002b1e92fbfe85b5f307d1479b4d" dependencies = [ "actix-codec", "actix-http", @@ -172,13 +171,13 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash", "bytes", "bytestring", "cfg-if", "cookie", - "derive_more", + "derive_more 2.0.1", "encoding_rs", + "foldhash", "futures-core", "futures-util", "impl-more", @@ -196,6 +195,7 @@ dependencies = [ "smallvec", "socket2", "time", + "tracing", "url", ] @@ -226,7 +226,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -237,7 +237,7 @@ checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -300,7 +300,7 @@ dependencies = [ "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -320,9 +320,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allo-isolate" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f67642eb6773fb42a95dd3b348c305ee18dee6642274c6b412d67e985e3befc" +checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550" dependencies = [ "anyhow", "atomic", @@ -408,7 +408,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -467,11 +467,11 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310c9bcae737a48ef5cdee3174184e6d548b292739ede61a1f955ef76a738861" +checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" dependencies = [ - "brotli 7.0.0", + "brotli", "futures-core", "memchr", "pin-project-lite", @@ -515,7 +515,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -547,7 +547,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.44", "tracing", ] @@ -559,7 +559,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -574,7 +574,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -588,13 +588,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -682,15 +682,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bigdecimal" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" +checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", @@ -737,9 +737,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" +checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3" dependencies = [ "arrayref", "arrayvec", @@ -750,28 +750,22 @@ dependencies = [ [[package]] name = "blind-rsa-signatures" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7a988a0de8f466ebc1b89f588aa8bdb28bd7e9a5f745da2b209e8f407d24ec" +checksum = "730c6814c82af047b85a3edc88a3ed0129c6046a6488188ccdf560384643e48f" dependencies = [ "derive-new", - "derive_more", + "derive_more 2.0.1", "digest", "hmac-sha256", "hmac-sha512", "num-integer", "num-traits", - "rand", + "rand 0.8.5", "rsa 0.8.2", "serde", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -781,6 +775,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037" +dependencies = [ + "objc2", +] + [[package]] name = "blocking" version = "1.6.1" @@ -794,17 +797,6 @@ dependencies = [ "piper", ] -[[package]] -name = "brotli" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - [[package]] name = "brotli" version = "7.0.0" @@ -864,9 +856,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytestring" @@ -879,9 +871,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" dependencies = [ "jobserver", "libc", @@ -1020,9 +1012,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.8" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf9dc8d4ef88e27a8cb23e85cb116403dedd57f7971964dc4b18ccead548901" +checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80" dependencies = [ "async-trait", "convert_case 0.6.0", @@ -1106,6 +1098,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -1212,7 +1213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1224,7 +1225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -1248,7 +1249,7 @@ dependencies = [ "curve25519-dalek-derive", "digest", "fiat-crypto", - "rand_core", + "rand_core 0.6.4", "rustc_version", "subtle", "zeroize", @@ -1262,14 +1263,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -1277,26 +1278,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1332,7 +1333,7 @@ checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1359,22 +1360,22 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "derive-new" -version = "0.5.9" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -1385,7 +1386,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1398,40 +1399,41 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] -name = "digest" -version = "0.10.7" +name = "derive_more" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "derive_more-impl", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "cfg-if", - "dirs-sys-next", + "convert_case 0.7.1", + "proc-macro2", + "quote", + "syn 2.0.100", + "unicode-xid", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "libc", - "redox_users", - "winapi", + "block-buffer", + "const-oid", + "crypto-common", + "subtle", ] [[package]] @@ -1442,7 +1444,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1493,7 +1495,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core", + "rand_core 0.6.4", "serde", "sha2", "subtle", @@ -1502,9 +1504,9 @@ dependencies = [ [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] @@ -1524,7 +1526,7 @@ dependencies = [ "hkdf", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1569,7 +1571,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1590,7 +1592,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1621,9 +1623,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1653,9 +1655,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", @@ -1693,11 +1695,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1721,9 +1723,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -1781,7 +1783,7 @@ dependencies = [ "md-5", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1792,9 +1794,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -1885,7 +1887,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1964,14 +1966,16 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -2007,7 +2011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2041,7 +2045,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.2.0", + "http 1.3.1", "indexmap", "slab", "tokio", @@ -2051,9 +2055,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" dependencies = [ "cfg-if", "crunchy", @@ -2064,9 +2068,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -2079,15 +2080,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "hashlink" version = "0.10.0" @@ -2185,7 +2177,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c3f1ae0a26c18d6469a70db1217136056261c4a244b09a755bc60bd4e055b67" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2200,8 +2192,8 @@ dependencies = [ "hpke-rs-crypto", "p256", "p384", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "sha2", "x25519-dalek", ] @@ -2219,9 +2211,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -2235,18 +2227,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.1", "http-body", "pin-project-lite", ] @@ -2273,7 +2265,7 @@ dependencies = [ "futures-channel", "futures-util", "h2 0.4.8", - "http 1.2.0", + "http 1.3.1", "http-body", "httparse", "itoa", @@ -2290,7 +2282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http 1.2.0", + "http 1.3.1", "hyper", "hyper-util", "rustls", @@ -2303,16 +2295,17 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -2322,16 +2315,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.0", ] [[package]] @@ -2384,9 +2378,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -2408,9 +2402,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -2429,9 +2423,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -2458,7 +2452,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2490,9 +2484,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", @@ -2535,9 +2529,9 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -2574,7 +2568,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2622,10 +2616,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -2703,9 +2698,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libfuzzer-sys" @@ -2757,6 +2752,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "litemap" version = "0.7.5" @@ -2792,9 +2793,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" @@ -2807,26 +2808,16 @@ dependencies = [ [[package]] name = "mac-notification-sys" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce8f34f3717aa37177e723df6c1fc5fb02b2a1087374ea3fe0ea42316dc8f91" +checksum = "0b95dfb34071d1592b45622bf93e315e3a72d414b6782aca9a015c12bec367ef" dependencies = [ "cc", - "dirs-next", - "objc-foundation", - "objc_id", + "objc2", + "objc2-foundation", "time", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "matchers" version = "0.1.0" @@ -2908,9 +2899,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" dependencies = [ "adler2", "simd-adler32", @@ -2990,9 +2981,9 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "notify-rust" -version = "4.11.5" +version = "4.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa3b9f2364a09bd359aa0206702882e208437450866a374d5372d64aece4029" +checksum = "4012c5a725bfa4cfd09c2f3abf29fb0b03a528d02787744e485c8014cdf6f1c0" dependencies = [ "futures-lite", "log", @@ -3034,7 +3025,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "serde", "smallvec", "zeroize", @@ -3054,7 +3045,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3109,32 +3100,41 @@ dependencies = [ ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" dependencies = [ - "malloc_buf", + "objc2-encode", ] [[package]] -name = "objc-foundation" -version = "0.1.1" +name = "objc2-core-foundation" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" dependencies = [ - "block", - "objc", - "objc_id", + "bitflags 2.9.0", + "objc2", ] [[package]] -name = "objc_id" -version = "0.1.1" +name = "objc2-encode" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" dependencies = [ - "objc", + "bitflags 2.9.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", ] [[package]] @@ -3148,9 +3148,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "opaque-debug" @@ -3173,7 +3173,7 @@ dependencies = [ "generic-array", "hkdf", "hmac", - "rand", + "rand 0.8.5", "serde", "subtle", "voprf", @@ -3183,7 +3183,7 @@ dependencies = [ [[package]] name = "openmls" version = "0.6.1" -source = "git+https://github.com/openmls/openmls#9b8c127435b14e69fcde576bef1b7c86bc5dc99f" +source = "git+https://github.com/openmls/openmls#de3099124572a49b80ac257b2be85de6f7b5ddd4" dependencies = [ "log", "openmls_traits", @@ -3196,7 +3196,7 @@ dependencies = [ [[package]] name = "openmls_memory_storage" version = "0.3.0" -source = "git+https://github.com/openmls/openmls#9b8c127435b14e69fcde576bef1b7c86bc5dc99f" +source = "git+https://github.com/openmls/openmls#de3099124572a49b80ac257b2be85de6f7b5ddd4" dependencies = [ "log", "openmls_traits", @@ -3208,7 +3208,7 @@ dependencies = [ [[package]] name = "openmls_rust_crypto" version = "0.3.0" -source = "git+https://github.com/openmls/openmls#9b8c127435b14e69fcde576bef1b7c86bc5dc99f" +source = "git+https://github.com/openmls/openmls#de3099124572a49b80ac257b2be85de6f7b5ddd4" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -3221,8 +3221,8 @@ dependencies = [ "openmls_memory_storage", "openmls_traits", "p256", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "sha2", "thiserror 2.0.12", @@ -3232,7 +3232,7 @@ dependencies = [ [[package]] name = "openmls_traits" version = "0.3.0" -source = "git+https://github.com/openmls/openmls#9b8c127435b14e69fcde576bef1b7c86bc5dc99f" +source = "git+https://github.com/openmls/openmls#de3099124572a49b80ac257b2be85de6f7b5ddd4" dependencies = [ "serde", "tls_codec", @@ -3334,7 +3334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -3386,9 +3386,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", "thiserror 2.0.12", @@ -3397,9 +3397,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -3407,22 +3407,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", @@ -3438,7 +3438,7 @@ dependencies = [ "actix-web-actors", "base64 0.22.1", "futures-util", - "http 1.2.0", + "http 1.3.1", "mls-assist", "phnxtypes", "privacypass", @@ -3462,7 +3462,7 @@ dependencies = [ "blake3", "bytes", "chrono", - "derive_more", + "derive_more 0.99.19", "flate2", "flutter_rust_bridge", "jni", @@ -3501,7 +3501,7 @@ dependencies = [ "opaque-ke", "phnxtypes", "privacypass", - "rand", + "rand 0.8.5", "serde", "serde_json", "sqlx", @@ -3520,7 +3520,7 @@ dependencies = [ "actix-rt", "anyhow", "chrono", - "derive_more", + "derive_more 0.99.19", "enumset", "image", "insta", @@ -3534,8 +3534,8 @@ dependencies = [ "phnxapiclient", "phnxserver_test_harness", "phnxtypes", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "serde_json", "sqlx", @@ -3597,8 +3597,8 @@ dependencies = [ "phnxcoreclient", "phnxserver", "phnxtypes", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "tokio", "tracing", "uuid", @@ -3621,8 +3621,8 @@ dependencies = [ "mls-assist", "opaque-ke", "privacypass", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "secrecy", "serde", "serde_json", @@ -3652,7 +3652,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3750,7 +3750,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -3792,11 +3792,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.24", ] [[package]] @@ -3817,10 +3817,10 @@ dependencies = [ "base64 0.22.1", "blind-rsa-signatures", "generic-array", - "http 1.2.0", + "http 1.3.1", "nom", "p384", - "rand", + "rand 0.8.5", "serde", "sha2", "thiserror 1.0.69", @@ -3832,9 +3832,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] @@ -3864,7 +3864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3918,7 +3918,7 @@ checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger 0.8.4", "log", - "rand", + "rand 0.8.5", ] [[package]] @@ -3934,11 +3934,12 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -3948,17 +3949,18 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand", + "getrandom 0.3.2", + "rand 0.9.0", "ring", "rustc-hash", "rustls", @@ -3972,9 +3974,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" dependencies = [ "cfg_aliases", "libc", @@ -3986,13 +3988,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -4000,8 +4008,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", ] [[package]] @@ -4011,7 +4030,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -4023,6 +4052,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -4049,8 +4087,8 @@ dependencies = [ "once_cell", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simd_helpers", "system-deps", "thiserror 1.0.69", @@ -4095,24 +4133,13 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.69", -] - [[package]] name = "regex" version = "1.11.1" @@ -4165,9 +4192,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "async-compression", "base64 0.22.1", @@ -4176,7 +4203,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.4.8", - "http 1.2.0", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -4266,7 +4293,7 @@ dependencies = [ "num-traits", "pkcs1 0.4.1", "pkcs8 0.9.0", - "rand_core", + "rand_core 0.6.4", "serde", "signature", "subtle", @@ -4275,9 +4302,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -4286,7 +4313,7 @@ dependencies = [ "num-traits", "pkcs1 0.7.5", "pkcs8 0.10.2", - "rand_core", + "rand_core 0.6.4", "signature", "spki 0.7.3", "subtle", @@ -4334,15 +4361,28 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "once_cell", "ring", @@ -4372,9 +4412,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", @@ -4440,31 +4480,31 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.16" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4494,7 +4534,7 @@ version = "0.1.0" source = "git+https://github.com/phnx-im/mimi-content?rev=71495dc8fc794111c381649c4c6cbc6e258a0135#71495dc8fc794111c381649c4c6cbc6e258a0135" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4505,7 +4545,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4582,7 +4622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -4629,18 +4669,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4706,7 +4746,7 @@ dependencies = [ "futures-io", "futures-util", "hashbrown 0.15.2", - "hashlink 0.10.0", + "hashlink", "indexmap", "log", "memchr", @@ -4737,7 +4777,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4760,7 +4800,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.99", + "syn 2.0.100", "tempfile", "tokio", "url", @@ -4797,8 +4837,8 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", - "rsa 0.9.7", + "rand 0.8.5", + "rsa 0.9.8", "serde", "sha1", "sha2", @@ -4839,7 +4879,7 @@ dependencies = [ "memchr", "num-bigint", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -4919,9 +4959,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -4945,7 +4985,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4991,15 +5031,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.5", "windows-sys 0.59.0", ] @@ -5029,7 +5068,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5040,7 +5079,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5075,9 +5114,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -5090,15 +5129,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -5156,7 +5195,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5166,14 +5205,14 @@ source = "git+https://github.com/boxdot/formats?rev=9846c69f91e732b493dcccf92f4c dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -5195,7 +5234,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5238,9 +5277,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -5343,7 +5382,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5433,7 +5472,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -5457,10 +5496,10 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.2.0", + "http 1.3.1", "httparse", "log", - "rand", + "rand 0.8.5", "rustls", "rustls-pki-types", "sha1", @@ -5536,6 +5575,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -5584,11 +5629,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", "serde", ] @@ -5639,7 +5684,7 @@ dependencies = [ "displaydoc", "elliptic-curve", "generic-array", - "rand_core", + "rand_core 0.6.4", "serde", "sha2", "subtle", @@ -5673,9 +5718,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -5708,7 +5753,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -5743,7 +5788,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5794,9 +5839,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -5845,23 +5890,27 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" dependencies = [ + "windows-implement 0.56.0", + "windows-interface 0.56.0", + "windows-result 0.1.2", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.56.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", ] [[package]] @@ -5872,7 +5921,18 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -5883,24 +5943,35 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result 0.2.0", - "windows-strings", - "windows-targets 0.52.6", + "windows-result 0.3.2", + "windows-strings 0.3.1", + "windows-targets 0.53.0", ] [[package]] @@ -5914,21 +5985,29 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", ] [[package]] @@ -6006,18 +6085,34 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows-version" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfbcc4996dd183ff1376a20ade1242da0d2dcaff83cc76710a588d24fd4c5db" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" dependencies = [ "windows-link", ] @@ -6040,6 +6135,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6058,6 +6159,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6076,12 +6183,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6100,6 +6219,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6118,6 +6243,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -6136,6 +6267,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6154,20 +6291,26 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] @@ -6191,20 +6334,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] [[package]] name = "xattr" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "rustix 1.0.5", ] [[package]] @@ -6219,13 +6361,13 @@ dependencies = [ [[package]] name = "yaml-rust2" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" +checksum = "818913695e83ece1f8d2a1c52d54484b7b46d0f9c06beeb2649b9da50d9b512d" dependencies = [ "arraydeque", "encoding_rs", - "hashlink 0.9.1", + "hashlink", ] [[package]] @@ -6248,7 +6390,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -6297,7 +6439,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "zbus_names", "zvariant", "zvariant_utils", @@ -6321,8 +6463,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -6333,7 +6483,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -6353,7 +6514,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -6374,7 +6535,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6396,7 +6557,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -6410,18 +6571,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.3" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", @@ -6475,7 +6636,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "zvariant_utils", ] @@ -6489,6 +6650,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn 2.0.99", + "syn 2.0.100", "winnow", ] diff --git a/apiclient/src/as_api/mod.rs b/apiclient/src/as_api/mod.rs index 9f515270f..54ef5d7ca 100644 --- a/apiclient/src/as_api/mod.rs +++ b/apiclient/src/as_api/mod.rs @@ -33,8 +33,9 @@ use phnxtypes::{ }, client_as_out::{ AsClientConnectionPackageResponseIn, AsCredentialsResponseIn, AsProcessResponseIn, - AsVersionedProcessResponseIn, ConnectionPackageIn, InitClientAdditionResponseIn, - InitUserRegistrationResponseIn, UserClientsResponseIn, + AsVersionedProcessResponseIn, ConnectionPackageIn, EncryptedUserProfile, + GetUserProfileParams, GetUserProfileResponse, InitClientAdditionResponseIn, + InitUserRegistrationResponseIn, UpdateUserProfileParamsTbs, UserClientsResponseIn, UserConnectionPackagesResponseIn, }, client_qs::DequeueMessagesResponse, @@ -160,6 +161,7 @@ impl ApiClient { connection_packages: Vec, opaque_registration_record: OpaqueRegistrationRecord, signing_key: &ClientSigningKey, + encrypted_user_profile: EncryptedUserProfile, ) -> Result<(), AsRequestError> { let tbs = FinishUserRegistrationParamsTbs { client_id: signing_key.credential().identity().clone(), @@ -167,6 +169,7 @@ impl ApiClient { initial_ratchet_secret, connection_packages, opaque_registration_record, + encrypted_user_profile, }; let payload = tbs.sign(signing_key)?; let params = AsRequestParamsOut::FinishUserRegistration(payload); @@ -182,6 +185,48 @@ impl ApiClient { }) } + pub async fn as_get_user_profile( + &self, + client_id: AsClientId, + ) -> Result { + let payload = GetUserProfileParams { client_id }; + let params = AsRequestParamsOut::GetUserProfile(payload); + self.prepare_and_send_as_message(params) + .await + // Check if the response is what we expected it to be. + .and_then(|response| { + if let AsProcessResponseIn::GetUserProfile(response) = response { + Ok(response) + } else { + Err(AsRequestError::UnexpectedResponse) + } + }) + } + + pub async fn as_update_user_profile( + &self, + client_id: AsClientId, + signing_key: &ClientSigningKey, + encrypted_user_profile: EncryptedUserProfile, + ) -> Result<(), AsRequestError> { + let payload = UpdateUserProfileParamsTbs { + client_id, + user_profile: encrypted_user_profile, + } + .sign(signing_key)?; + let params = AsRequestParamsOut::UpdateUserProfile(payload); + self.prepare_and_send_as_message(params) + .await + // Check if the response is what we expected it to be. + .and_then(|response| { + if matches!(response, AsProcessResponseIn::Ok) { + Ok(()) + } else { + Err(AsRequestError::UnexpectedResponse) + } + }) + } + pub async fn as_delete_user( &self, user_name: QualifiedUserName, diff --git a/apiclient/src/ds_api/mod.rs b/apiclient/src/ds_api/mod.rs index 797e90360..a49e246e8 100644 --- a/apiclient/src/ds_api/mod.rs +++ b/apiclient/src/ds_api/mod.rs @@ -9,9 +9,7 @@ use crate::version::{extract_api_version_negotiation, negotiate_api_version}; use super::*; use mls_assist::{ messages::AssistedMessageOut, - openmls::prelude::{ - GroupEpoch, GroupId, LeafNodeIndex, MlsMessageOut, RatchetTreeIn, tls_codec::Serialize, - }, + openmls::prelude::{GroupEpoch, GroupId, LeafNodeIndex, MlsMessageOut, tls_codec::Serialize}, }; use phnxtypes::{ LibraryError, @@ -34,7 +32,7 @@ use phnxtypes::{ DsProcessResponseIn, DsRequestParamsOut, DsVersionedProcessResponseIn, DsVersionedRequestParamsOut, ExternalCommitInfoIn, GroupOperationParamsOut, JoinConnectionGroupParamsOut, ResyncParamsOut, SelfRemoveParamsOut, - SendMessageParamsOut, UpdateParamsOut, + SendMessageParamsOut, UpdateParamsOut, WelcomeInfoIn, }, }, time::TimeStamp, @@ -182,7 +180,7 @@ impl ApiClient { epoch: GroupEpoch, group_state_ear_key: &GroupStateEarKey, signing_key: &PseudonymousCredentialSigningKey, - ) -> Result { + ) -> Result { let payload = WelcomeInfoParams { sender: signing_key.credential().verifying_key().clone(), group_id, @@ -196,8 +194,8 @@ impl ApiClient { .await // Check if the response is what we expected it to be. .and_then(|response| { - if let DsProcessResponseIn::WelcomeInfo(ratchet_tree) = response { - Ok(ratchet_tree) + if let DsProcessResponseIn::WelcomeInfo(welcome_info) = response { + Ok(welcome_info) } else { Err(DsRequestError::UnexpectedResponse) } diff --git a/app/pubspec.lock b/app/pubspec.lock index aff264c9b..fae83f172 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: archive - sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" + sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.5" args: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.4" + version: "8.9.5" characters: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" + sha256: f3a3d48a36d1640b4dca22a086f26b426c246925a80eddc2953120775fbcf86a url: "https://pub.dev" source: hosted - version: "0.5.1+12" + version: "0.5.1+13" file_selector_ios: dependency: transitive description: @@ -462,10 +462,10 @@ packages: dependency: transitive description: name: image - sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.5.4" image_picker: dependency: "direct main" description: @@ -478,10 +478,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" + sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" url: "https://pub.dev" source: hosted - version: "0.8.12+21" + version: "0.8.12+22" image_picker_for_web: dependency: transitive description: @@ -502,10 +502,10 @@ packages: dependency: transitive description: name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_macos: dependency: transitive description: @@ -667,10 +667,10 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" path: dependency: "direct main" description: @@ -691,10 +691,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.16" path_provider_foundation: dependency: transitive description: @@ -834,18 +834,18 @@ packages: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.4" pub_semver: dependency: transitive description: name: pub_semver - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" pubspec_parse: dependency: transitive description: @@ -1151,10 +1151,10 @@ packages: dependency: transitive description: name: win32 - sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f url: "https://pub.dev" source: hosted - version: "5.11.0" + version: "5.12.0" xdg_directories: dependency: transitive description: diff --git a/app/rust_builder/cargokit/build_tool/pubspec.lock b/app/rust_builder/cargokit/build_tool/pubspec.lock index 343bdd369..8bc105611 100644 --- a/app/rust_builder/cargokit/build_tool/pubspec.lock +++ b/app/rust_builder/cargokit/build_tool/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" adaptive_number: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" args: dependency: "direct main" description: @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" collection: dependency: "direct main" description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.11.1" crypto: dependency: "direct main" description: @@ -101,18 +101,18 @@ packages: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" github: dependency: "direct main" description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" hex: dependency: "direct main" description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: @@ -165,26 +165,26 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" json_annotation: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" lints: dependency: "direct dev" description: @@ -205,26 +205,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.17" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.16.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" node_preamble: dependency: transitive description: @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" path: dependency: "direct main" description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shelf: dependency: transitive description: @@ -293,34 +293,34 @@ packages: dependency: transitive description: name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.0.0" source_map_stack_trace: dependency: transitive description: name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" source_maps: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: "direct main" description: @@ -333,58 +333,58 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: "direct dev" description: name: test - sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.24.6" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.6.8" toml: dependency: "direct main" description: @@ -397,10 +397,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" version: dependency: "direct main" description: @@ -413,34 +413,50 @@ packages: dependency: transitive description: name: vm_service - sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "11.9.0" + version: "15.0.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.2" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" yaml: dependency: "direct main" description: @@ -450,4 +466,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" diff --git a/backend/.sqlx/query-28b419939a08f57c86836f0fc1f8a9735debc1f68d7e46004a03328c103bc3a6.json b/backend/.sqlx/query-28b419939a08f57c86836f0fc1f8a9735debc1f68d7e46004a03328c103bc3a6.json deleted file mode 100644 index f7174183d..000000000 --- a/backend/.sqlx/query-28b419939a08f57c86836f0fc1f8a9735debc1f68d7e46004a03328c103bc3a6.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO as_user_records (user_name, password_file) VALUES ($1, $2)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Bytea" - ] - }, - "nullable": [] - }, - "hash": "28b419939a08f57c86836f0fc1f8a9735debc1f68d7e46004a03328c103bc3a6" -} diff --git a/backend/.sqlx/query-5c918b741638016d4dd8cc9940581d7eae69d7719543fe95279d3fe13825f5b5.json b/backend/.sqlx/query-5c918b741638016d4dd8cc9940581d7eae69d7719543fe95279d3fe13825f5b5.json new file mode 100644 index 000000000..58bbbe711 --- /dev/null +++ b/backend/.sqlx/query-5c918b741638016d4dd8cc9940581d7eae69d7719543fe95279d3fe13825f5b5.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE as_user_records\n SET password_file = $1, encrypted_user_profile = $2\n WHERE user_name = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bytea", + { + "Custom": { + "name": "aead_ciphertext", + "kind": { + "Composite": [ + [ + "ciphertext", + "Bytea" + ], + [ + "nonce", + "Bytea" + ] + ] + } + } + }, + "Text" + ] + }, + "nullable": [] + }, + "hash": "5c918b741638016d4dd8cc9940581d7eae69d7719543fe95279d3fe13825f5b5" +} diff --git a/backend/.sqlx/query-75767ac0c4fd0d80b05374023441177850ad84c37d647ae99f623f6f0438ebf6.json b/backend/.sqlx/query-75767ac0c4fd0d80b05374023441177850ad84c37d647ae99f623f6f0438ebf6.json new file mode 100644 index 000000000..4071516fb --- /dev/null +++ b/backend/.sqlx/query-75767ac0c4fd0d80b05374023441177850ad84c37d647ae99f623f6f0438ebf6.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n password_file AS \"password_file: _\",\n encrypted_user_profile AS \"encrypted_user_profile: _\"\n FROM as_user_records\n WHERE user_name = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "password_file: _", + "type_info": "Bytea" + }, + { + "ordinal": 1, + "name": "encrypted_user_profile: _", + "type_info": { + "Custom": { + "name": "aead_ciphertext", + "kind": { + "Composite": [ + [ + "ciphertext", + "Bytea" + ], + [ + "nonce", + "Bytea" + ] + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "75767ac0c4fd0d80b05374023441177850ad84c37d647ae99f623f6f0438ebf6" +} diff --git a/backend/.sqlx/query-973891122d0736c69c439a140d5cab6a2e59e00f0e52d14a214f5c48b8cbc0d8.json b/backend/.sqlx/query-973891122d0736c69c439a140d5cab6a2e59e00f0e52d14a214f5c48b8cbc0d8.json deleted file mode 100644 index 5bb667187..000000000 --- a/backend/.sqlx/query-973891122d0736c69c439a140d5cab6a2e59e00f0e52d14a214f5c48b8cbc0d8.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n password_file AS \"password_file: _\"\n FROM as_user_records\n WHERE user_name = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "password_file: _", - "type_info": "Bytea" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "973891122d0736c69c439a140d5cab6a2e59e00f0e52d14a214f5c48b8cbc0d8" -} diff --git a/backend/.sqlx/query-fb077ef59b81b3659e0bc280679606b0b6d620387abd433d96c7acec525f8bd6.json b/backend/.sqlx/query-fb077ef59b81b3659e0bc280679606b0b6d620387abd433d96c7acec525f8bd6.json new file mode 100644 index 000000000..4f48c915a --- /dev/null +++ b/backend/.sqlx/query-fb077ef59b81b3659e0bc280679606b0b6d620387abd433d96c7acec525f8bd6.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO as_user_records (user_name, password_file, encrypted_user_profile) VALUES ($1, $2, $3)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Bytea", + { + "Custom": { + "name": "aead_ciphertext", + "kind": { + "Composite": [ + [ + "ciphertext", + "Bytea" + ], + [ + "nonce", + "Bytea" + ] + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "fb077ef59b81b3659e0bc280679606b0b6d620387abd433d96c7acec525f8bd6" +} diff --git a/backend/migrations/20240927070412_create_initial_as_tables.sql b/backend/migrations/20240927070412_create_initial_as_tables.sql index e6642bc14..d24368e66 100644 --- a/backend/migrations/20240927070412_create_initial_as_tables.sql +++ b/backend/migrations/20240927070412_create_initial_as_tables.sql @@ -7,9 +7,15 @@ CREATE TABLE as_batched_keys( voprf_server BYTEA NOT NULL ); +CREATE TYPE aead_ciphertext AS ( + ciphertext BYTEA, + nonce BYTEA +); + CREATE TABLE as_user_records( user_name TEXT PRIMARY KEY, - password_file BYTEA NOT NULL + password_file BYTEA NOT NULL, + encrypted_user_profile aead_ciphertext NOT NULL ); CREATE TYPE qualified_user_name AS ( diff --git a/backend/migrations/20240927094408_create_initial_qs_tables.sql b/backend/migrations/20240927094408_create_initial_qs_tables.sql index 461585729..82460f70f 100644 --- a/backend/migrations/20240927094408_create_initial_qs_tables.sql +++ b/backend/migrations/20240927094408_create_initial_qs_tables.sql @@ -28,11 +28,6 @@ CREATE TABLE qs_user_records ( verifying_key BYTEA NOT NULL ); -CREATE TYPE aead_ciphertext AS ( - ciphertext BYTEA, - nonce BYTEA -); - CREATE TABLE qs_client_records ( client_id uuid PRIMARY KEY, user_id uuid NOT NULL, diff --git a/backend/src/auth_service/client_api/mod.rs b/backend/src/auth_service/client_api/mod.rs index c52616cd7..db31f4d14 100644 --- a/backend/src/auth_service/client_api/mod.rs +++ b/backend/src/auth_service/client_api/mod.rs @@ -18,6 +18,7 @@ pub mod client; pub mod key_packages; pub mod privacypass; pub mod user; +pub mod user_profile; impl AuthService { pub(crate) async fn as_init_two_factor_auth( diff --git a/backend/src/auth_service/client_api/user.rs b/backend/src/auth_service/client_api/user.rs index 0c0e1934d..8778d997b 100644 --- a/backend/src/auth_service/client_api/user.rs +++ b/backend/src/auth_service/client_api/user.rs @@ -124,6 +124,7 @@ impl AuthService { initial_ratchet_secret: initial_ratchet_key, connection_packages, opaque_registration_record, + encrypted_user_profile, } = params; // Look up the initial client's ClientCredential in the ephemeral DB based on the user_name @@ -139,12 +140,17 @@ impl AuthService { let password_file = ServerRegistration::finish(opaque_registration_record.client_message); // Create the user entry with the information given in the request - UserRecord::new_and_store(&self.db_pool, client_id.user_name(), &password_file) - .await - .map_err(|e| { - tracing::error!("Storage provider error: {:?}", e); - FinishUserRegistrationError::StorageError - })?; + UserRecord::new_and_store( + &self.db_pool, + client_id.user_name(), + &password_file, + &encrypted_user_profile, + ) + .await + .map_err(|e| { + tracing::error!("Storage provider error: {:?}", e); + FinishUserRegistrationError::StorageError + })?; // Verify and store connection packages let as_intermediate_credentials = IntermediateCredential::load_all(&self.db_pool) diff --git a/backend/src/auth_service/client_api/user_profile.rs b/backend/src/auth_service/client_api/user_profile.rs new file mode 100644 index 000000000..c4346c872 --- /dev/null +++ b/backend/src/auth_service/client_api/user_profile.rs @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2025 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use phnxtypes::{ + errors::auth_service::{GetUserProfileError, UpdateUserProfileError}, + messages::client_as_out::{ + GetUserProfileParams, GetUserProfileResponse, UpdateUserProfileParamsTbs, + }, +}; + +use crate::auth_service::{AuthService, user_record::UserRecord}; + +impl AuthService { + pub(crate) async fn as_get_user_profile( + &self, + params: GetUserProfileParams, + ) -> Result { + let GetUserProfileParams { client_id } = params; + + let user_record = UserRecord::load(&self.db_pool, client_id.user_name()) + .await + .map_err(|e| { + tracing::error!("Error loading user record: {:?}", e); + GetUserProfileError::StorageError + })? + .ok_or(GetUserProfileError::UserNotFound)?; + + let user_profile = user_record.into_encrypted_user_profile(); + + let response = GetUserProfileResponse { + encrypted_user_profile: user_profile, + }; + + Ok(response) + } + + pub(crate) async fn as_update_user_profile( + &self, + params: UpdateUserProfileParamsTbs, + ) -> Result<(), UpdateUserProfileError> { + let UpdateUserProfileParamsTbs { + client_id, + user_profile, + } = params; + + let mut user_record = UserRecord::load(&self.db_pool, client_id.user_name()) + .await + .map_err(|e| { + tracing::error!("Error loading user record: {:?}", e); + UpdateUserProfileError::StorageError + })? + .ok_or(UpdateUserProfileError::UserNotFound)?; + + user_record.set_user_profile(user_profile); + + user_record.update(&self.db_pool).await.map_err(|e| { + tracing::error!("Error updating user record: {:?}", e); + UpdateUserProfileError::StorageError + })?; + + Ok(()) + } +} diff --git a/backend/src/auth_service/mod.rs b/backend/src/auth_service/mod.rs index d94aa1478..dc883542e 100644 --- a/backend/src/auth_service/mod.rs +++ b/backend/src/auth_service/mod.rs @@ -23,6 +23,7 @@ use phnxtypes::{ SUPPORTED_AS_API_VERSIONS, UserClientsResponse, UserConnectionPackagesResponse, VerifiedAsRequestParams, }, + client_as_out::GetUserProfileResponse, client_qs::DequeueMessagesResponse, }, }; @@ -215,6 +216,14 @@ impl AuthService { .as_init_user_registration(params) .await .map(AsProcessResponse::InitUserRegistration)?, + VerifiedAsRequestParams::GetUserProfile(params) => self + .as_get_user_profile(params) + .await + .map(AsProcessResponse::GetUserProfile)?, + VerifiedAsRequestParams::UpdateUserProfile(params) => { + self.as_update_user_profile(params).await?; + AsProcessResponse::Ok + } }; Ok(AsVersionedProcessResponse::with_version( @@ -280,4 +289,5 @@ pub enum AsProcessResponse { UserClients(UserClientsResponse), AsCredentials(AsCredentialsResponse), InitUserRegistration(InitUserRegistrationResponse), + GetUserProfile(GetUserProfileResponse), } diff --git a/backend/src/auth_service/user_record.rs b/backend/src/auth_service/user_record.rs index c2b97f10b..22af77ca4 100644 --- a/backend/src/auth_service/user_record.rs +++ b/backend/src/auth_service/user_record.rs @@ -3,7 +3,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use opaque_ke::ServerRegistration; -use phnxtypes::{crypto::OpaqueCiphersuite, identifiers::QualifiedUserName}; +use phnxtypes::{ + crypto::OpaqueCiphersuite, identifiers::QualifiedUserName, + messages::client_as_out::EncryptedUserProfile, +}; use crate::errors::StorageError; @@ -12,16 +15,19 @@ use crate::errors::StorageError; pub(super) struct UserRecord { user_name: QualifiedUserName, password_file: ServerRegistration, + encrypted_user_profile: EncryptedUserProfile, } impl UserRecord { fn new( user_name: QualifiedUserName, password_file: ServerRegistration, + encrypted_user_profile: EncryptedUserProfile, ) -> Self { Self { user_name, password_file, + encrypted_user_profile, } } @@ -29,8 +35,13 @@ impl UserRecord { connection: impl sqlx::PgExecutor<'_>, user_name: &QualifiedUserName, opaque_record: &ServerRegistration, + encrypted_user_profile: &EncryptedUserProfile, ) -> Result { - let user_record = Self::new(user_name.clone(), opaque_record.clone()); + let user_record = Self::new( + user_name.clone(), + opaque_record.clone(), + encrypted_user_profile.clone(), + ); user_record.store(connection).await?; Ok(user_record) } @@ -43,14 +54,25 @@ impl UserRecord { pub(super) fn into_password_file(self) -> ServerRegistration { self.password_file } + + pub(super) fn into_encrypted_user_profile(self) -> EncryptedUserProfile { + self.encrypted_user_profile + } + + pub(super) fn set_user_profile(&mut self, encrypted_user_profile: EncryptedUserProfile) { + self.encrypted_user_profile = encrypted_user_profile; + } } pub(crate) mod persistence { + use opaque_ke::ServerRegistration; use phnxtypes::{ codec::{BlobDecoded, BlobEncoded}, + crypto::OpaqueCiphersuite, identifiers::QualifiedUserName, + messages::client_as_out::EncryptedUserProfile, }; - use sqlx::{PgExecutor, query, query_scalar}; + use sqlx::{PgExecutor, query, query_as}; use crate::errors::StorageError; @@ -63,20 +85,51 @@ pub(crate) mod persistence { connection: impl PgExecutor<'_>, user_name: &QualifiedUserName, ) -> Result, StorageError> { - let record = query_scalar!( + struct AsUserRecord { + password_file: BlobDecoded>, + encrypted_user_profile: EncryptedUserProfile, + } + + let record = query_as!( + AsUserRecord, r#"SELECT - password_file AS "password_file: _" + password_file AS "password_file: _", + encrypted_user_profile AS "encrypted_user_profile: _" FROM as_user_records WHERE user_name = $1"#, user_name.to_string(), ) .fetch_optional(connection) .await?; - Ok(record.map(|BlobDecoded(password_file)| { - UserRecord::new(user_name.clone(), password_file) + Ok(record.map(|record| { + let BlobDecoded(password_file) = record.password_file; + UserRecord::new( + user_name.clone(), + password_file, + record.encrypted_user_profile, + ) })) } + /// Update the AsUserRecord for a given UserId. + pub(crate) async fn update( + &self, + connection: impl PgExecutor<'_>, + ) -> Result<(), StorageError> { + let password_file = BlobEncoded(&self.password_file); + query!( + "UPDATE as_user_records + SET password_file = $1, encrypted_user_profile = $2 + WHERE user_name = $3", + password_file as _, + self.encrypted_user_profile as _, + self.user_name.to_string() + ) + .execute(connection) + .await?; + Ok(()) + } + /// Create a new user with the given user name. If a user with the given user /// name already exists, an error is returned. pub(super) async fn store( @@ -85,9 +138,10 @@ pub(crate) mod persistence { ) -> Result<(), StorageError> { let password_file = BlobEncoded(&self.password_file); query!( - "INSERT INTO as_user_records (user_name, password_file) VALUES ($1, $2)", + "INSERT INTO as_user_records (user_name, password_file, encrypted_user_profile) VALUES ($1, $2, $3)", self.user_name.to_string(), password_file as _, + self.encrypted_user_profile as _ ) .execute(connection) .await?; @@ -123,7 +177,10 @@ pub(crate) mod persistence { use opaque_ke::{ ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup, }; - use phnxtypes::{codec::PhnxCodec, crypto::OpaqueCiphersuite}; + use phnxtypes::{ + codec::PhnxCodec, crypto::OpaqueCiphersuite, + messages::client_as_out::EncryptedUserProfile, + }; use rand::{CryptoRng, RngCore, SeedableRng, rngs::StdRng}; use sqlx::PgPool; use uuid::Uuid; @@ -163,9 +220,11 @@ pub(crate) mod persistence { pub(crate) async fn store_random_user_record(pool: &PgPool) -> anyhow::Result { let user_name: QualifiedUserName = format!("{}@example.com", Uuid::new_v4()).parse()?; let password_file = generate_password_file(&user_name, &mut rand::thread_rng())?; + let encrypted_user_profile = EncryptedUserProfile::dummy(); let record = UserRecord { user_name, password_file, + encrypted_user_profile, }; record.store(pool).await?; Ok(record) diff --git a/backend/src/ds/group_operation.rs b/backend/src/ds/group_operation.rs index c99885cbf..acc4e97fc 100644 --- a/backend/src/ds/group_operation.rs +++ b/backend/src/ds/group_operation.rs @@ -17,7 +17,7 @@ use mls_assist::{ use phnxtypes::{ crypto::{ - ear::keys::{EncryptedIdentityLinkKey, GroupStateEarKey}, + ear::keys::{EncryptedIdentityLinkKey, EncryptedUserProfileKey, GroupStateEarKey}, hpke::{HpkeEncryptable, JoinerInfoEncryptionKey}, }, errors::GroupOperationError, @@ -139,8 +139,7 @@ impl DsGroupState { return Err(GroupOperationError::InvalidMessage); }; - let add_users_state = - validate_added_users(staged_commit, &aad_payload, add_users_info)?; + let add_users_state = validate_added_users(staged_commit, aad_payload, add_users_info)?; Some(add_users_state) }; @@ -155,12 +154,13 @@ impl DsGroupState { // Collect the encrypted client information and the client queue // config of the original client. We need this later to create // the new client profile. - let encrypted_identity_link_key = self + let sender_profile = self .member_profiles .get(&original_index) - .ok_or(GroupOperationError::InvalidMessage)? - .encrypted_identity_link_key - .clone(); + .ok_or(GroupOperationError::InvalidMessage)?; + let encrypted_identity_link_key = + sender_profile.encrypted_identity_link_key.clone(); + let encrypted_user_profile_key = sender_profile.encrypted_user_profile_key.clone(); // Get the queue config from the leaf node extensions. let client_queue_config = staged_commit .update_path_leaf_node() @@ -179,7 +179,11 @@ impl DsGroupState { _ => None, }) .ok_or(GroupOperationError::InvalidMessage)??; - Some((encrypted_identity_link_key, client_queue_config)) + Some(( + encrypted_identity_link_key, + encrypted_user_profile_key, + client_queue_config, + )) } _ => None, }; @@ -222,8 +226,11 @@ impl DsGroupState { } // Process resync operations - if let Some((encrypted_identity_link_key, client_queue_config)) = - external_sender_information + if let Some(( + encrypted_identity_link_key, + encrypted_user_profile_key, + client_queue_config, + )) = external_sender_information { // The original client profile was already removed. We just have to // add the new one. @@ -233,6 +240,7 @@ impl DsGroupState { client_queue_config, activity_time: TimeStamp::now(), activity_epoch: self.group().epoch(), + encrypted_user_profile_key, }; self.member_profiles .insert(sender_index.leaf_index(), client_profile); @@ -251,7 +259,9 @@ impl DsGroupState { added_users: &[(AddedUserInfo, EncryptedWelcomeAttributionInfo)], ) -> Result<(), GroupOperationError> { let mut client_profiles = vec![]; - for ((key_package, encrypted_identity_link_key), _) in added_users.iter() { + for ((key_package, encrypted_identity_link_key, encrypted_user_profile_key), _) in + added_users.iter() + { let member = self .group() .members() @@ -276,6 +286,7 @@ impl DsGroupState { let client_profile = MemberProfile { leaf_index, encrypted_identity_link_key: encrypted_identity_link_key.clone(), + encrypted_user_profile_key: encrypted_user_profile_key.clone(), client_queue_config: client_queue_config.clone(), activity_time: TimeStamp::now(), activity_epoch: self.group().epoch(), @@ -298,7 +309,7 @@ impl DsGroupState { welcome: &AssistedWelcome, ) -> Result, GroupOperationError> { let mut fan_out_messages = vec![]; - for ((key_package, _), attribution_info) in added_users.into_iter() { + for ((key_package, _, _), attribution_info) in added_users.into_iter() { let client_queue_config = QsReference::tls_deserialize_exact_bytes( key_package .leaf_node() @@ -322,6 +333,7 @@ impl DsGroupState { group_state_ear_key: group_state_ear_key.clone(), encrypted_identity_link_keys: self.encrypted_identity_link_keys(), ratchet_tree: self.group().export_ratchet_tree(), + encrypted_user_profile_keys: self.encrypted_user_profile_keys(), } .encrypt(&encryption_key, info, aad); let welcome_bundle = WelcomeBundle { @@ -352,7 +364,11 @@ impl DsGroupState { } } -type AddedUserInfo = (KeyPackage, EncryptedIdentityLinkKey); +type AddedUserInfo = ( + KeyPackage, + EncryptedIdentityLinkKey, + EncryptedUserProfileKey, +); struct AddUsersState { added_users: Vec<(AddedUserInfo, EncryptedWelcomeAttributionInfo)>, @@ -361,7 +377,7 @@ struct AddUsersState { fn validate_added_users( staged_commit: &StagedCommit, - aad_payload: &GroupOperationParamsAad, + aad_payload: GroupOperationParamsAad, add_users_info: AddUsersInfo, ) -> Result { let number_of_added_users = staged_commit.add_proposals().count(); @@ -398,7 +414,9 @@ fn validate_added_users( let added_users = staged_commit .add_proposals() .map(|ap| ap.add_proposal().key_package().clone()) - .zip(aad_payload.new_encrypted_identity_link_keys.clone()) + .zip(aad_payload.new_encrypted_identity_link_keys) + .zip(aad_payload.new_encrypted_user_profile_keys) + .map(|((kp, eilk), eupk)| (kp, eilk, eupk)) .zip(add_users_info.encrypted_welcome_attribution_infos) .collect(); diff --git a/backend/src/ds/group_state/mod.rs b/backend/src/ds/group_state/mod.rs index a5cf080f6..93124f3a5 100644 --- a/backend/src/ds/group_state/mod.rs +++ b/backend/src/ds/group_state/mod.rs @@ -19,7 +19,7 @@ use phnxtypes::{ crypto::{ ear::{ Ciphertext, EarDecryptable, EarEncryptable, - keys::{EncryptedIdentityLinkKey, GroupStateEarKey}, + keys::{EncryptedIdentityLinkKey, EncryptedUserProfileKey, GroupStateEarKey}, }, errors::{DecryptionError, EncryptionError}, }, @@ -46,6 +46,7 @@ pub(super) struct MemberProfile { pub(super) client_queue_config: QsReference, pub(super) activity_time: TimeStamp, pub(super) activity_epoch: GroupEpoch, + pub(super) encrypted_user_profile_key: EncryptedUserProfileKey, } /// The `DsGroupState` is the per-group state that the DS persists. @@ -64,6 +65,7 @@ impl DsGroupState { provider: MlsAssistRustCrypto, group: Group, creator_encrypted_identity_link_key: EncryptedIdentityLinkKey, + creator_encrypted_user_profile_key: EncryptedUserProfileKey, creator_queue_config: QsReference, ) -> Self { let creator_client_profile = MemberProfile { @@ -72,6 +74,7 @@ impl DsGroupState { activity_time: TimeStamp::now(), activity_epoch: 0u64.into(), leaf_index: LeafNodeIndex::new(0u32), + encrypted_user_profile_key: creator_encrypted_user_profile_key, }; let client_profiles = [(LeafNodeIndex::new(0u32), creator_client_profile)].into(); Self { @@ -103,10 +106,12 @@ impl DsGroupState { let group_info = self.group().group_info().clone(); let ratchet_tree = self.group().export_ratchet_tree(); let encrypted_identity_link_keys = self.encrypted_identity_link_keys(); + let encrypted_user_profile_keys = self.encrypted_user_profile_keys(); ExternalCommitInfo { group_info, ratchet_tree, encrypted_identity_link_keys, + encrypted_user_profile_keys, } } @@ -119,6 +124,15 @@ impl DsGroupState { .collect() } + /// Create a vector of encrypted user profile keys from the current list of + /// client records. + pub(super) fn encrypted_user_profile_keys(&self) -> Vec { + self.member_profiles + .values() + .map(|client_profile| client_profile.encrypted_user_profile_key.clone()) + .collect() + } + pub(super) fn encrypt( self, ear_key: &GroupStateEarKey, diff --git a/backend/src/ds/join_connection_group.rs b/backend/src/ds/join_connection_group.rs index f0973ac7a..f437a402a 100644 --- a/backend/src/ds/join_connection_group.rs +++ b/backend/src/ds/join_connection_group.rs @@ -112,6 +112,7 @@ impl DsGroupState { client_queue_config: params.qs_client_reference, activity_time: TimeStamp::now(), activity_epoch: self.group().epoch(), + encrypted_user_profile_key: aad_payload.encrypted_user_profile_key, }; self.member_profiles.insert(sender, member_profile); diff --git a/backend/src/ds/process.rs b/backend/src/ds/process.rs index 7a5bf2fcc..1049c6daa 100644 --- a/backend/src/ds/process.rs +++ b/backend/src/ds/process.rs @@ -163,7 +163,7 @@ use uuid::Uuid; use phnxtypes::{ codec::PhnxCodec, crypto::{ - ear::keys::{EncryptedIdentityLinkKey, GroupStateEarKey}, + ear::keys::{EncryptedIdentityLinkKey, EncryptedUserProfileKey, GroupStateEarKey}, signatures::{keys::LeafVerifyingKey, signable::Verifiable}, }, errors::{DsProcessingError, version::VersionError}, @@ -259,6 +259,7 @@ impl Ds { encrypted_identity_link_key, creator_qs_reference: creator_queue_config, group_info, + encrypted_user_profile_key, } = create_group_params; let MlsMessageBodyIn::GroupInfo(group_info) = group_info.clone().extract() else { return Err(DsProcessingError::InvalidMessage); @@ -270,6 +271,7 @@ impl Ds { provider, group, encrypted_identity_link_key.clone(), + encrypted_user_profile_key.clone(), creator_queue_config.clone(), ); (GroupData::NewGroup(reserved_group_id), group_state) @@ -376,11 +378,12 @@ impl Ds { let ratchet_tree = group_state .welcome_info(welcome_info_params) .ok_or(DsProcessingError::NoWelcomeInfoFound)?; - ( - None, - DsProcessResponse::WelcomeInfo(ratchet_tree.clone()), - vec![], - ) + let welcome_info = WelcomeInfo { + ratchet_tree: ratchet_tree.clone(), + encrypted_identity_link_keys: group_state.encrypted_identity_link_keys(), + encrypted_user_profile_keys: group_state.encrypted_user_profile_keys(), + }; + (None, DsProcessResponse::WelcomeInfo(welcome_info), vec![]) } DsGroupRequestParams::CreateGroupParams(_) => (None, DsProcessResponse::Ok, vec![]), DsGroupRequestParams::_UpdateQsClientReference => { @@ -551,15 +554,22 @@ pub struct ExternalCommitInfo { pub group_info: GroupInfo, pub ratchet_tree: RatchetTree, pub encrypted_identity_link_keys: Vec, + pub encrypted_user_profile_keys: Vec, +} + +#[derive(Debug, TlsSerialize, TlsSize)] +pub struct WelcomeInfo { + pub ratchet_tree: RatchetTree, + pub encrypted_identity_link_keys: Vec, + pub encrypted_user_profile_keys: Vec, } -#[expect(clippy::large_enum_variant)] #[derive(Debug, TlsSerialize, TlsSize)] #[repr(u8)] pub enum DsProcessResponse { Ok, FanoutTimestamp(TimeStamp), - WelcomeInfo(RatchetTree), + WelcomeInfo(WelcomeInfo), ExternalCommitInfo(ExternalCommitInfo), GroupId(GroupId), } diff --git a/coreclient/.sqlx/query-094feee4584fe553b048645ac6657254c0d4e091eebea522822208005075cf38.json b/coreclient/.sqlx/query-094feee4584fe553b048645ac6657254c0d4e091eebea522822208005075cf38.json new file mode 100644 index 000000000..f5e5d73f3 --- /dev/null +++ b/coreclient/.sqlx/query-094feee4584fe553b048645ac6657254c0d4e091eebea522822208005075cf38.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "SELECT\n ik.key_index as \"index: _\",\n ik.key_value as \"key: _\",\n ik.base_secret as \"base_secret: _\"\n FROM own_key_indices oki\n JOIN indexed_keys ik ON oki.key_index = ik.key_index\n WHERE oki.key_type = ?", + "describe": { + "columns": [ + { + "name": "index: _", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "key: _", + "ordinal": 1, + "type_info": "Blob" + }, + { + "name": "base_secret: _", + "ordinal": 2, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "094feee4584fe553b048645ac6657254c0d4e091eebea522822208005075cf38" +} diff --git a/coreclient/.sqlx/query-0c3b957fd18fd409d563d75db91f4ef129aef5222a028487218c9dcc08e146b2.json b/coreclient/.sqlx/query-0c3b957fd18fd409d563d75db91f4ef129aef5222a028487218c9dcc08e146b2.json new file mode 100644 index 000000000..9d17035bc --- /dev/null +++ b/coreclient/.sqlx/query-0c3b957fd18fd409d563d75db91f4ef129aef5222a028487218c9dcc08e146b2.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM indexed_keys WHERE key_index = $1", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "0c3b957fd18fd409d563d75db91f4ef129aef5222a028487218c9dcc08e146b2" +} diff --git a/coreclient/.sqlx/query-a11e8f331dcbd9845736340a39ee1dc0a4684a4f6e020f9c95b9a5cba17f7e8b.json b/coreclient/.sqlx/query-2c5823929ace6b56806ca3e023bee1e32cf9dff0f1009f3b923a90bd402f32e4.json similarity index 80% rename from coreclient/.sqlx/query-a11e8f331dcbd9845736340a39ee1dc0a4684a4f6e020f9c95b9a5cba17f7e8b.json rename to coreclient/.sqlx/query-2c5823929ace6b56806ca3e023bee1e32cf9dff0f1009f3b923a90bd402f32e4.json index bf2b45d8d..cffcdf382 100644 --- a/coreclient/.sqlx/query-a11e8f331dcbd9845736340a39ee1dc0a4684a4f6e020f9c95b9a5cba17f7e8b.json +++ b/coreclient/.sqlx/query-2c5823929ace6b56806ca3e023bee1e32cf9dff0f1009f3b923a90bd402f32e4.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT\n user_name AS \"user_name: _\",\n conversation_id AS \"conversation_id: _\",\n clients AS \"clients: _\",\n wai_ear_key AS \"wai_ear_key: _\",\n friendship_token AS \"friendship_token: _\",\n key_package_ear_key AS \"key_package_ear_key: _\",\n connection_key AS \"connection_key: _\"\n FROM contacts", + "query": "SELECT\n user_name AS \"user_name: _\",\n conversation_id AS \"conversation_id: _\",\n clients AS \"clients: _\",\n wai_ear_key AS \"wai_ear_key: _\",\n friendship_token AS \"friendship_token: _\",\n key_package_ear_key AS \"key_package_ear_key: _\",\n connection_key AS \"connection_key: _\",\n user_profile_key_index AS \"user_profile_key_index: _\"\n FROM contacts", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "name": "connection_key: _", "ordinal": 6, "type_info": "Blob" + }, + { + "name": "user_profile_key_index: _", + "ordinal": 7, + "type_info": "Blob" } ], "parameters": { @@ -49,8 +54,9 @@ false, false, false, + false, false ] }, - "hash": "a11e8f331dcbd9845736340a39ee1dc0a4684a4f6e020f9c95b9a5cba17f7e8b" + "hash": "2c5823929ace6b56806ca3e023bee1e32cf9dff0f1009f3b923a90bd402f32e4" } diff --git a/coreclient/.sqlx/query-4ae748575bdbd059b6961f5b37190b805319d05bb35eb25b27ed2164701f1e8f.json b/coreclient/.sqlx/query-4ae748575bdbd059b6961f5b37190b805319d05bb35eb25b27ed2164701f1e8f.json deleted file mode 100644 index 5ae371f13..000000000 --- a/coreclient/.sqlx/query-4ae748575bdbd059b6961f5b37190b805319d05bb35eb25b27ed2164701f1e8f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT OR IGNORE INTO users\n (user_name, display_name, profile_picture) VALUES (?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "4ae748575bdbd059b6961f5b37190b805319d05bb35eb25b27ed2164701f1e8f" -} diff --git a/coreclient/.sqlx/query-74444da1493a47b29af6b55aa4c7c3a3bc033e76b85fa2fa38a2e573f826d668.json b/coreclient/.sqlx/query-74444da1493a47b29af6b55aa4c7c3a3bc033e76b85fa2fa38a2e573f826d668.json new file mode 100644 index 000000000..1a65d8b94 --- /dev/null +++ b/coreclient/.sqlx/query-74444da1493a47b29af6b55aa4c7c3a3bc033e76b85fa2fa38a2e573f826d668.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT\n base_secret AS \"base_secret: _\",\n key_value AS \"key: _\",\n key_index AS \"index: _\"\n FROM indexed_keys\n WHERE key_index = ?\n LIMIT 1", + "describe": { + "columns": [ + { + "name": "base_secret: _", + "ordinal": 0, + "type_info": "Blob" + }, + { + "name": "key: _", + "ordinal": 1, + "type_info": "Blob" + }, + { + "name": "index: _", + "ordinal": 2, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "74444da1493a47b29af6b55aa4c7c3a3bc033e76b85fa2fa38a2e573f826d668" +} diff --git a/coreclient/.sqlx/query-4c2d92f0f13ff8241491c49659bd80d4038add85f2afadfe01775a702fc16e93.json b/coreclient/.sqlx/query-83512c7147da583f3dbbe1be8726673b62d68605238bb2fbc363c5e53a020da1.json similarity index 79% rename from coreclient/.sqlx/query-4c2d92f0f13ff8241491c49659bd80d4038add85f2afadfe01775a702fc16e93.json rename to coreclient/.sqlx/query-83512c7147da583f3dbbe1be8726673b62d68605238bb2fbc363c5e53a020da1.json index d7b64fce0..80d026a6d 100644 --- a/coreclient/.sqlx/query-4c2d92f0f13ff8241491c49659bd80d4038add85f2afadfe01775a702fc16e93.json +++ b/coreclient/.sqlx/query-83512c7147da583f3dbbe1be8726673b62d68605238bb2fbc363c5e53a020da1.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT\n user_name AS \"user_name: _\",\n conversation_id AS \"conversation_id: _\",\n clients AS \"clients: _\",\n wai_ear_key AS \"wai_ear_key: _\",\n friendship_token AS \"friendship_token: _\",\n key_package_ear_key AS \"key_package_ear_key: _\",\n connection_key AS \"connection_key: _\"\n FROM contacts WHERE user_name = ?", + "query": "SELECT\n user_name AS \"user_name: _\",\n conversation_id AS \"conversation_id: _\",\n clients AS \"clients: _\",\n wai_ear_key AS \"wai_ear_key: _\",\n friendship_token AS \"friendship_token: _\",\n key_package_ear_key AS \"key_package_ear_key: _\",\n connection_key AS \"connection_key: _\",\n user_profile_key_index AS \"user_profile_key_index: _\"\n FROM contacts WHERE user_name = ?", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "name": "connection_key: _", "ordinal": 6, "type_info": "Blob" + }, + { + "name": "user_profile_key_index: _", + "ordinal": 7, + "type_info": "Blob" } ], "parameters": { @@ -49,8 +54,9 @@ false, false, false, + false, false ] }, - "hash": "4c2d92f0f13ff8241491c49659bd80d4038add85f2afadfe01775a702fc16e93" + "hash": "83512c7147da583f3dbbe1be8726673b62d68605238bb2fbc363c5e53a020da1" } diff --git a/coreclient/.sqlx/query-96a268287f3ca534dadbdd1a17077e3125a4f35c7a93f285df790da1b1b05142.json b/coreclient/.sqlx/query-96a268287f3ca534dadbdd1a17077e3125a4f35c7a93f285df790da1b1b05142.json new file mode 100644 index 000000000..ecc2ae3f9 --- /dev/null +++ b/coreclient/.sqlx/query-96a268287f3ca534dadbdd1a17077e3125a4f35c7a93f285df790da1b1b05142.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT OR IGNORE INTO indexed_keys (base_secret, key_value, key_index)\n VALUES ($1, $2, $3)", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "96a268287f3ca534dadbdd1a17077e3125a4f35c7a93f285df790da1b1b05142" +} diff --git a/coreclient/.sqlx/query-096fbad192ab01313ddabc38b6312a6337e226f75902fd3b5ab4076d0bae0cdd.json b/coreclient/.sqlx/query-d4f55c3cbe6659508584b92c6615c1f50f71ae86e8381e64cd4657cef4f2ed64.json similarity index 52% rename from coreclient/.sqlx/query-096fbad192ab01313ddabc38b6312a6337e226f75902fd3b5ab4076d0bae0cdd.json rename to coreclient/.sqlx/query-d4f55c3cbe6659508584b92c6615c1f50f71ae86e8381e64cd4657cef4f2ed64.json index a5d536e08..f5a514ba1 100644 --- a/coreclient/.sqlx/query-096fbad192ab01313ddabc38b6312a6337e226f75902fd3b5ab4076d0bae0cdd.json +++ b/coreclient/.sqlx/query-d4f55c3cbe6659508584b92c6615c1f50f71ae86e8381e64cd4657cef4f2ed64.json @@ -1,12 +1,12 @@ { "db_name": "SQLite", - "query": "INSERT INTO contacts\n (user_name, conversation_id, clients, wai_ear_key, friendship_token,\n key_package_ear_key, connection_key)\n VALUES (?, ?, ?, ?, ?, ?, ?)", + "query": "INSERT INTO contacts\n (user_name, conversation_id, clients, wai_ear_key, friendship_token,\n key_package_ear_key, connection_key, user_profile_key_index)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "describe": { "columns": [], "parameters": { - "Right": 7 + "Right": 8 }, "nullable": [] }, - "hash": "096fbad192ab01313ddabc38b6312a6337e226f75902fd3b5ab4076d0bae0cdd" + "hash": "d4f55c3cbe6659508584b92c6615c1f50f71ae86e8381e64cd4657cef4f2ed64" } diff --git a/coreclient/.sqlx/query-ec96f77f5015788384c1334659d35ac19a00f748c231e58bde10435ea5079102.json b/coreclient/.sqlx/query-ec96f77f5015788384c1334659d35ac19a00f748c231e58bde10435ea5079102.json new file mode 100644 index 000000000..64ee7b9ae --- /dev/null +++ b/coreclient/.sqlx/query-ec96f77f5015788384c1334659d35ac19a00f748c231e58bde10435ea5079102.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT OR IGNORE INTO own_key_indices (key_index, key_type) VALUES ($1, $2)", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "ec96f77f5015788384c1334659d35ac19a00f748c231e58bde10435ea5079102" +} diff --git a/coreclient/migrations/20250409073637_add_indexed_keys.sql b/coreclient/migrations/20250409073637_add_indexed_keys.sql new file mode 100644 index 000000000..bb488ebfe --- /dev/null +++ b/coreclient/migrations/20250409073637_add_indexed_keys.sql @@ -0,0 +1,43 @@ +-- SPDX-FileCopyrightText: 2025 Phoenix R&D GmbH +-- +-- SPDX-License-Identifier: AGPL-3.0-or-later + +-- Create table to store any indexed keys +CREATE TABLE IF NOT EXISTS indexed_keys ( + key_index BLOB NOT NULL PRIMARY KEY, + key_value BLOB NOT NULL, + base_secret BLOB NOT NULL +); + +-- Create table to store indices of own keys +CREATE TABLE IF NOT EXISTS own_key_indices ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key_index BLOB NOT NULL, + key_type TEXT CHECK(key_type IN ('user_profile_key')) NOT NULL, + FOREIGN KEY (key_index) REFERENCES indexed_keys(key_index) +); + +-- Recreate contacts table to add foreign key constraint +DROP TABLE IF EXISTS contacts; + +CREATE TABLE IF NOT EXISTS contacts ( + user_name TEXT NOT NULL PRIMARY KEY, + conversation_id BLOB NOT NULL, + clients TEXT NOT NULL, + wai_ear_key BLOB NOT NULL, + friendship_token BLOB NOT NULL, + key_package_ear_key BLOB NOT NULL, + connection_key BLOB NOT NULL, + user_profile_key_index BLOB NOT NULL, + FOREIGN KEY (conversation_id) REFERENCES conversations (conversation_id), + FOREIGN KEY (user_profile_key_index) REFERENCES indexed_keys (key_index) +); + +CREATE TRIGGER IF NOT EXISTS delete_keys AFTER DELETE ON contacts FOR EACH ROW BEGIN +-- Delete user profile keys if the corresponding contact is deleted. Since key +-- indexes include the user name in their derivation, they are unique per user +-- and we don't need to check if they are used by another user (or ourselves). +DELETE FROM indexed_keys +WHERE + fingerprint = OLD.user_profile_key_index; +END; diff --git a/coreclient/src/clients/add_contact.rs b/coreclient/src/clients/add_contact.rs index c4aec1921..9823972c4 100644 --- a/coreclient/src/clients/add_contact.rs +++ b/coreclient/src/clients/add_contact.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use anyhow::{Context, ensure}; +use anyhow::ensure; use openmls::group::GroupId; use phnxtypes::{ codec::PhnxCodec, @@ -23,7 +23,7 @@ use crate::{ Conversation, ConversationAttributes, ConversationId, PartialContact, UserProfile, clients::connection_establishment::{ConnectionEstablishmentPackageTbs, FriendshipPackage}, groups::{Group, PartialCreateGroupParams, openmls_provider::PhnxOpenMlsProvider}, - key_stores::{MemoryUserKeyStore, as_credentials::AsCredentials}, + key_stores::{MemoryUserKeyStore, as_credentials::AsCredentials, indexed_keys::UserProfileKey}, store::StoreNotifier, }; @@ -65,9 +65,6 @@ impl CoreUser { }) .await?; - let user_profile = UserProfile::load(self.pool(), self.user_name()) - .await? - .context("missing user profile")?; let client_reference = self.create_own_client_reference(); let local_partial_contact = local_group @@ -75,7 +72,6 @@ impl CoreUser { self.pool(), &mut notifier, &self.inner.key_store, - user_profile, client_reference, user_name.clone(), ) @@ -245,7 +241,6 @@ impl LocalGroup { pool: &SqlitePool, notifier: &mut StoreNotifier, key_store: &MemoryUserKeyStore, - own_user_profile: UserProfile, own_client_reference: QsReference, user_name: QualifiedUserName, ) -> anyhow::Result { @@ -256,12 +251,14 @@ impl LocalGroup { verified_connection_packages, } = self; + let own_user_profile_key = UserProfileKey::load_own(pool).await?; + let friendship_package = FriendshipPackage { friendship_token: key_store.friendship_token.clone(), key_package_ear_key: key_store.key_package_ear_key.clone(), connection_key: key_store.connection_key.clone(), wai_ear_key: key_store.wai_ear_key.clone(), - user_profile: own_user_profile, + user_profile_base_secret: own_user_profile_key.base_secret().clone(), }; let friendship_package_ear_key = FriendshipPackageEarKey::random()?; @@ -275,11 +272,13 @@ impl LocalGroup { .store(pool, notifier) .await?; - // Store the user profile of the partial contact (we don't have a - // display name or a profile picture yet) - UserProfile::new(user_name, None, None) - .store(pool, notifier) - .await?; + // Check if we already have a user profile for this user. If not, store + // a basic one without display name and profile picture. + if UserProfile::load(pool, &user_name).await?.is_none() { + UserProfile::new(user_name, None, None) + .store(pool, notifier) + .await?; + }; // Create a connection establishment package let connection_establishment_package = ConnectionEstablishmentPackageTbs { @@ -292,7 +291,9 @@ impl LocalGroup { } .sign(&key_store.signing_key)?; - let params = partial_params.into_params(own_client_reference); + let encrypted_user_profile_key = + own_user_profile_key.encrypt(group.identity_link_wrapper_key())?; + let params = partial_params.into_params(own_client_reference, encrypted_user_profile_key); Ok(LocalPartialContact { group, diff --git a/coreclient/src/clients/connection_establishment.rs b/coreclient/src/clients/connection_establishment.rs index 59737c654..0eca536fe 100644 --- a/coreclient/src/clients/connection_establishment.rs +++ b/coreclient/src/clients/connection_establishment.rs @@ -30,7 +30,7 @@ use tls_codec::{ DeserializeBytes, Serialize as TlsSerializeTrait, TlsDeserializeBytes, TlsSerialize, TlsSize, }; -use crate::user_profiles::UserProfile; +use crate::key_stores::indexed_keys::UserProfileBaseSecret; #[derive(Debug, TlsSerialize, TlsSize, Clone)] pub struct ConnectionEstablishmentPackageTbs { @@ -172,7 +172,7 @@ pub(crate) struct FriendshipPackage { pub(crate) key_package_ear_key: KeyPackageEarKey, pub(crate) connection_key: ConnectionKey, pub(crate) wai_ear_key: WelcomeAttributionInfoEarKey, - pub(crate) user_profile: UserProfile, + pub(crate) user_profile_base_secret: UserProfileBaseSecret, } impl GenericSerializable for FriendshipPackage { diff --git a/coreclient/src/clients/conversations.rs b/coreclient/src/clients/conversations.rs index d2e773647..0ef7cb380 100644 --- a/coreclient/src/clients/conversations.rs +++ b/coreclient/src/clients/conversations.rs @@ -185,7 +185,9 @@ mod create_conversation_flow { use openmls::group::GroupId; use openmls_traits::OpenMlsProvider; use phnxtypes::{ - codec::PhnxCodec, credentials::keys::ClientSigningKey, crypto::kdf::keys::ConnectionKey, + codec::PhnxCodec, + credentials::keys::ClientSigningKey, + crypto::{ear::keys::EncryptedUserProfileKey, kdf::keys::ConnectionKey}, identifiers::QsReference, }; @@ -193,6 +195,7 @@ mod create_conversation_flow { Conversation, ConversationAttributes, ConversationId, clients::api_clients::ApiClients, groups::{Group, GroupData, PartialCreateGroupParams, client_auth_info::GroupMembership}, + key_stores::indexed_keys::UserProfileKey, store::StoreNotifier, }; @@ -274,6 +277,10 @@ mod create_conversation_flow { attributes, } = self; + let user_profile_key = UserProfileKey::load_own(&mut *connection).await?; + let encrypted_user_profile_key = + user_profile_key.encrypt(group.identity_link_wrapper_key())?; + group_membership.store(&mut *connection).await?; group.store(&mut *connection).await?; @@ -283,6 +290,7 @@ mod create_conversation_flow { Ok(StoredGroup { group, + encrypted_user_profile_key, partial_params, conversation_id: conversation.id(), }) @@ -291,6 +299,7 @@ mod create_conversation_flow { pub(super) struct StoredGroup { group: Group, + encrypted_user_profile_key: EncryptedUserProfileKey, partial_params: PartialCreateGroupParams, conversation_id: ConversationId, } @@ -303,11 +312,12 @@ mod create_conversation_flow { ) -> Result { let Self { group, + encrypted_user_profile_key, partial_params, conversation_id, } = self; - let params = partial_params.into_params(client_reference); + let params = partial_params.into_params(client_reference, encrypted_user_profile_key); api_clients .default_client()? .ds_create_group(params, group.leaf_signer(), group.group_state_ear_key()) diff --git a/coreclient/src/clients/create_user.rs b/coreclient/src/clients/create_user.rs index 535c2e204..7438fa098 100644 --- a/coreclient/src/clients/create_user.rs +++ b/coreclient/src/clients/create_user.rs @@ -6,6 +6,7 @@ use crate::{ groups::client_auth_info::StorableClientCredential, key_stores::{ as_credentials::AsCredentials, + indexed_keys::UserProfileKey, queue_ratchets::{StorableAsQueueRatchet, StorableQsQueueRatchet}, }, }; @@ -24,6 +25,7 @@ use phnxtypes::{ }, messages::{ client_as::ConnectionPackage, + client_as_out::EncryptedUserProfile, client_qs::CreateUserRecordResponse, push_token::{EncryptedPushToken, PushToken}, }, @@ -254,7 +256,7 @@ impl PostRegistrationInitState { let qs_client_signing_key = QsClientSigningKey::random()?; let qs_user_signing_key = QsUserSigningKey::generate()?; - // TODO: The following five keys should be derived from a single + // TODO: The following keys should be derived from a single // friendship key. Once that's done, remove the random constructors. let friendship_token = FriendshipToken::random()?; let key_package_ear_key = KeyPackageEarKey::random()?; @@ -286,6 +288,16 @@ impl PostRegistrationInitState { qs_client_id_encryption_key: qs_encryption_key, }; + let user_profile_key = UserProfileKey::random(user_name)?; + user_profile_key + .store_own(pool.acquire().await?.as_mut()) + .await?; + + let user_profile = UserProfile::load(pool, user_name) + .await? + .context("Own user profile not found")?; + let encrypted_user_profile = user_profile.encrypt(&user_profile_key)?; + // TODO: For now, we use the same ConnectionDecryptionKey for all // connection packages. @@ -304,6 +316,7 @@ impl PostRegistrationInitState { let unfinalized_registration_state = UnfinalizedRegistrationState { key_store, + encrypted_user_profile, opaque_client_message: client_registration_finish_result .message .serialize() @@ -334,6 +347,7 @@ impl PostRegistrationInitState { #[derive(Serialize, Deserialize)] pub(crate) struct UnfinalizedRegistrationState { key_store: MemoryUserKeyStore, + encrypted_user_profile: EncryptedUserProfile, opaque_client_message: Vec, server_url: String, as_initial_ratchet_secret: RatchetSecret, @@ -349,6 +363,7 @@ impl UnfinalizedRegistrationState { ) -> Result { let UnfinalizedRegistrationState { key_store, + encrypted_user_profile, opaque_client_message, server_url, as_initial_ratchet_secret, @@ -372,6 +387,7 @@ impl UnfinalizedRegistrationState { connection_packages, opaque_registration_record, &key_store.signing_key, + encrypted_user_profile, ) .await?; let as_registered_user_state = AsRegisteredUserState { diff --git a/coreclient/src/clients/invite_users.rs b/coreclient/src/clients/invite_users.rs index 49c6e8f56..e4a9c5333 100644 --- a/coreclient/src/clients/invite_users.rs +++ b/coreclient/src/clients/invite_users.rs @@ -92,7 +92,7 @@ mod invite_users_flow { for invited_user in invited_users { // Get the WAI keys and client credentials for the invited users. - let contact = Contact::load(pool, invited_user) + let contact = Contact::load(pool.acquire().await?.as_mut(), invited_user) .await? .with_context(|| format!("Can't find contact with user name {invited_user}"))?; contact_wai_keys.push(contact.wai_ear_key().clone()); diff --git a/coreclient/src/clients/mod.rs b/coreclient/src/clients/mod.rs index dbad0e8f6..ee78e380e 100644 --- a/coreclient/src/clients/mod.rs +++ b/coreclient/src/clients/mod.rs @@ -81,6 +81,7 @@ pub mod store; #[cfg(test)] mod tests; mod update_key; +mod user_profile; pub(crate) const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; @@ -282,9 +283,7 @@ impl CoreUser { }; user_profile.set_profile_picture(Some(Asset::Value(new_image))); } - let mut notifier = self.store_notifier(); - user_profile.update(self.pool(), &mut notifier).await?; - notifier.notify(); + self.update_user_profile(&user_profile).await?; Ok(()) } diff --git a/coreclient/src/clients/process/mod.rs b/coreclient/src/clients/process/mod.rs index fc9a4f2b3..5a82c5974 100644 --- a/coreclient/src/clients/process/mod.rs +++ b/coreclient/src/clients/process/mod.rs @@ -3,8 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use super::{ - AsCredentials, Asset, Contact, Conversation, ConversationAttributes, ConversationId, CoreUser, - EarEncryptable, FriendshipPackage, TimestampedMessage, UserProfile, anyhow, + AsCredentials, Contact, Conversation, ConversationAttributes, ConversationId, CoreUser, + EarEncryptable, FriendshipPackage, TimestampedMessage, anyhow, }; pub mod process_as; diff --git a/coreclient/src/clients/process/process_as.rs b/coreclient/src/clients/process/process_as.rs index 38de55759..900e84a2c 100644 --- a/coreclient/src/clients/process/process_as.rs +++ b/coreclient/src/clients/process/process_as.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use anyhow::Result; +use anyhow::{Context, Result}; use openmls::prelude::MlsMessageOut; use phnxtypes::{ crypto::hpke::HpkeDecryptable, @@ -18,17 +18,18 @@ use tls_codec::DeserializeBytes; use tracing::error; use crate::{ + UserProfile, clients::connection_establishment::{ ConnectionEstablishmentPackageIn, ConnectionEstablishmentPackageTbs, }, - groups::Group, - key_stores::leaf_keys::LeafKeys, + groups::{Group, ProfileInfo}, + key_stores::{indexed_keys::UserProfileKey, leaf_keys::LeafKeys}, store::StoreNotifier, }; use super::{ - AsCredentials, Asset, Contact, Conversation, ConversationAttributes, ConversationId, CoreUser, - EarEncryptable, FriendshipPackage, UserProfile, anyhow, + AsCredentials, Contact, Conversation, ConversationAttributes, ConversationId, CoreUser, + EarEncryptable, FriendshipPackage, anyhow, }; use crate::key_stores::queue_ratchets::StorableAsQueueRatchet; @@ -61,33 +62,45 @@ impl CoreUser { .parse_and_verify_connection_establishment_package(ecep) .await?; - // Load user profile - let own_user_profile = self.load_own_user_profile().await?; - // Prepare group - let (leaf_keys, aad, qgid) = self.prepare_group(&cep_tbs, own_user_profile)?; + let own_user_profile_key = UserProfileKey::load_own(self.pool()).await?; + let (leaf_keys, aad, qgid) = self.prepare_group(&cep_tbs, &own_user_profile_key)?; // Fetch external commit info let eci = self.fetch_external_commit_info(&cep_tbs, &qgid).await?; // Join group - let (group, commit, group_info) = self + let (group, commit, group_info, mut member_profile_info) = self .join_group_externally(eci, &cep_tbs, leaf_keys, aad) .await?; + // There should be only one user profile + let contact_profile_info = member_profile_info + .pop() + .context("No user profile returned when joining connection group")?; + + debug_assert!( + member_profile_info.is_empty(), + "More than one user profile returned when joining connection group" + ); + + // Fetch user profile + let user_profile = self.fetch_user_profile(contact_profile_info).await?; + // Create conversation - let (mut conversation, contact) = + let (mut conversation, contact, user_profile_key) = self.create_connection_conversation(&group, &cep_tbs)?; let mut notifier = self.store_notifier(); - // Store group, conversation & contact + // Store group, conversation, contact & user profile self.store_group_conversation_contact( &mut notifier, &group, &mut conversation, contact, - &cep_tbs, + &user_profile, + &user_profile_key, ) .await?; @@ -138,7 +151,7 @@ impl CoreUser { fn prepare_group( &self, cep_tbs: &ConnectionEstablishmentPackageTbs, - own_user_profile: UserProfile, + own_user_profile_key: &UserProfileKey, ) -> Result<(LeafKeys, InfraAadMessage, QualifiedGroupId)> { // We create a new group and signal that fact to the user, // so the user can decide if they want to accept the @@ -153,12 +166,15 @@ impl CoreUser { .identity_link_key() .encrypt(&cep_tbs.connection_group_identity_link_wrapper_key)?; + let encrypted_user_profile_key = + own_user_profile_key.encrypt(&cep_tbs.connection_group_identity_link_wrapper_key)?; + let encrypted_friendship_package = FriendshipPackage { friendship_token: self.inner.key_store.friendship_token.clone(), key_package_ear_key: self.inner.key_store.key_package_ear_key.clone(), connection_key: self.inner.key_store.connection_key.clone(), wai_ear_key: self.inner.key_store.wai_ear_key.clone(), - user_profile: own_user_profile, + user_profile_base_secret: own_user_profile_key.base_secret().clone(), } .encrypt(&cep_tbs.friendship_package_ear_key)?; @@ -166,6 +182,7 @@ impl CoreUser { InfraAadPayload::JoinConnectionGroup(JoinConnectionGroupParamsAad { encrypted_friendship_package, encrypted_identity_link_key, + encrypted_user_profile_key, }) .into(); let qgid = @@ -174,13 +191,6 @@ impl CoreUser { Ok((leaf_keys, aad, qgid)) } - async fn load_own_user_profile(&self) -> Result { - // We unwrap here, because we know that the user exists. - Ok(UserProfile::load(self.pool(), self.user_name()) - .await? - .unwrap()) - } - async fn fetch_external_commit_info( &self, cep_tbs: &ConnectionEstablishmentPackageTbs, @@ -203,9 +213,9 @@ impl CoreUser { cep_tbs: &ConnectionEstablishmentPackageTbs, leaf_keys: LeafKeys, aad: InfraAadMessage, - ) -> Result<(Group, MlsMessageOut, MlsMessageOut)> { + ) -> Result<(Group, MlsMessageOut, MlsMessageOut, Vec)> { let (leaf_signer, identity_link_key) = leaf_keys.into_parts(); - let (group, commit, group_info) = Group::join_group_externally( + let (group, commit, group_info, member_profile_info) = Group::join_group_externally( self.pool(), &self.inner.api_clients, eci, @@ -217,36 +227,27 @@ impl CoreUser { self.inner.key_store.signing_key.credential(), ) .await?; - Ok((group, commit, group_info)) + Ok((group, commit, group_info, member_profile_info)) } fn create_connection_conversation( &self, group: &Group, cep_tbs: &ConnectionEstablishmentPackageTbs, - ) -> Result<(Conversation, Contact)> { + ) -> Result<(Conversation, Contact, UserProfileKey)> { let sender_client_id = cep_tbs.sender_client_credential.identity(); - let conversation_picture_option = cep_tbs - .friendship_package - .user_profile - .profile_picture() - .map(|asset| match asset { - Asset::Value(value) => value.to_owned(), - }); + let conversation = Conversation::new_connection_conversation( group.group_id().clone(), sender_client_id.user_name().clone(), - ConversationAttributes::new( - sender_client_id.user_name().to_string(), - conversation_picture_option, - ), + ConversationAttributes::new(sender_client_id.user_name().to_string(), None), )?; - let contact = Contact::from_friendship_package( + let (contact, user_profile_key) = Contact::from_friendship_package( sender_client_id.clone(), conversation.id(), cep_tbs.friendship_package.clone(), - ); - Ok((conversation, contact)) + )?; + Ok((conversation, contact, user_profile_key)) } async fn store_group_conversation_contact( @@ -255,20 +256,19 @@ impl CoreUser { group: &Group, conversation: &mut Conversation, contact: Contact, - cep_tbs: &ConnectionEstablishmentPackageTbs, + user_profile: &UserProfile, + user_profile_key: &UserProfileKey, ) -> Result<()> { let mut connection = self.pool().acquire().await?; group.store(&mut *connection).await?; conversation.store(&mut *connection, notifier).await?; - // Store the user profile of the sender. - cep_tbs - .friendship_package - .user_profile - .store_or_ignore(&mut *connection, notifier) - .await?; + + user_profile.store(&mut *connection, notifier).await?; + + user_profile_key.store(&mut *connection).await?; + // TODO: For now, we automatically confirm conversations. conversation.confirm(&mut *connection, notifier).await?; - // TODO: Here, we want to store a contact contact.store(&mut *connection, notifier).await?; Ok(()) } diff --git a/coreclient/src/clients/process/process_qs.rs b/coreclient/src/clients/process/process_qs.rs index ae0c62456..7ed0292f6 100644 --- a/coreclient/src/clients/process/process_qs.rs +++ b/coreclient/src/clients/process/process_qs.rs @@ -22,11 +22,14 @@ use phnxtypes::{ }; use tls_codec::DeserializeBytes; -use crate::{ConversationMessage, PartialContact, conversations::ConversationType, groups::Group}; +use crate::{ + ConversationMessage, PartialContact, conversations::ConversationType, groups::Group, + key_stores::indexed_keys::UserProfileKey, +}; use super::{ - Asset, Conversation, ConversationAttributes, ConversationId, CoreUser, FriendshipPackage, - TimestampedMessage, UserProfile, anyhow, + Conversation, ConversationAttributes, ConversationId, CoreUser, FriendshipPackage, + TimestampedMessage, anyhow, }; use crate::key_stores::queue_ratchets::StorableQsQueueRatchet; @@ -100,7 +103,7 @@ impl CoreUser { ) -> Result { // WelcomeBundle Phase 1: Join the group. This might involve // loading AS credentials or fetching them from the AS. - let group = Group::join_group( + let (group, member_profile_info) = Group::join_group( welcome_bundle, &self.inner.key_store.wai_ear_key, self.pool(), @@ -109,15 +112,24 @@ impl CoreUser { .await?; let group_id = group.group_id().clone(); - // WelcomeBundle Phase 2: Store the user profiles of the group + // WelcomeBundle Phase 2: Fetch the user profiles of the group members + // and decrypt them. + + // TODO: This can fail in some cases. If it does, we should fetch and + // process messages and then try again. + let mut user_profiles = Vec::with_capacity(member_profile_info.len()); + for profile_info in member_profile_info { + let user_profile = self.fetch_user_profile(profile_info).await?; + user_profiles.push(user_profile); + } + + // WelcomeBundle Phase 3: Store the user profiles of the group // members if they don't exist yet and store the group and the // new conversation. let conversation_id = self .with_transaction_and_notifier(async |connection, notifier| { - for user_name in group.members(&mut *connection).await.into_iter() { - UserProfile::new(user_name, None, None) - .store(&mut *connection, notifier) - .await?; + for user_profile in user_profiles { + user_profile.store(&mut *connection, notifier).await?; } // Set the conversation attributes according to the group's @@ -270,16 +282,15 @@ impl CoreUser { if let ConversationType::UnconfirmedConnection(user_name) = conversation.conversation_type() { - let user_name = user_name.clone(); // Check if it was an external commit and if the user name matches if !matches!(sender, Sender::NewMemberCommit) - && sender_client_id.user_name() == &user_name + && sender_client_id.user_name() == user_name { // TODO: Handle the fact that an unexpected user joined the connection group. } // UnconfirmedConnection Phase 1: Load up the partial contact and decrypt the // friendship package - let partial_contact = PartialContact::load(self.pool(), &user_name) + let partial_contact = PartialContact::load(self.pool(), user_name) .await? .ok_or_else(|| anyhow!("No partial contact found for user name {}", user_name))?; @@ -301,23 +312,16 @@ impl CoreUser { &encrypted_friendship_package, )?; - // UnconfirmedConnection Phase 2: Store the user profile of the sender and the contact. - friendship_package - .user_profile - .update(self.pool(), &mut notifier) - .await?; - - // Set the picture of the conversation to the one of the contact. - let conversation_picture_option = friendship_package - .user_profile - .profile_picture() - .map(|asset| match asset { - Asset::Value(value) => value.to_owned(), - }); + let user_profile_key = UserProfileKey::from_base_secret( + friendship_package.user_profile_base_secret.clone(), + user_name, + )?; - conversation - .set_conversation_picture(self.pool(), &mut notifier, conversation_picture_option) + // UnconfirmedConnection Phase 2: Fetch the user profile. + let user_profile = self + .fetch_user_profile((sender_client_id.clone(), user_profile_key.clone())) .await?; + // Now we can turn the partial contact into a full one. partial_contact .mark_as_complete( @@ -325,6 +329,8 @@ impl CoreUser { &mut notifier, friendship_package, sender_client_id.clone(), + &user_profile, + &user_profile_key, ) .await?; diff --git a/coreclient/src/clients/user_profile.rs b/coreclient/src/clients/user_profile.rs new file mode 100644 index 000000000..1e3617b0f --- /dev/null +++ b/coreclient/src/clients/user_profile.rs @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2025 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use phnxtypes::{ + crypto::ear::{EarDecryptable, EarEncryptable}, + messages::client_as_out::GetUserProfileResponse, +}; + +use crate::{UserProfile, groups::ProfileInfo, key_stores::indexed_keys::UserProfileKey}; + +use super::CoreUser; + +impl CoreUser { + pub async fn update_user_profile(&self, user_profile: &UserProfile) -> anyhow::Result<()> { + let mut notifier = self.store_notifier(); + + // Phase 1: Store the user profile update in the database + user_profile.update(self.pool(), &mut notifier).await?; + + notifier.notify(); + + let user_profile_key = UserProfileKey::load_own(self.pool()).await?; + + // Phase 2: Encrypt the user profile + let encrypted_user_profile = user_profile.encrypt(&user_profile_key)?; + + // Phase 3: Send the updated profile to the server + let api_client = self.inner.api_clients.default_client()?; + + api_client + .as_update_user_profile( + self.as_client_id(), + &self.inner.key_store.signing_key, + encrypted_user_profile, + ) + .await?; + + Ok(()) + } + + pub(crate) async fn fetch_user_profile( + &self, + profile_info: impl Into, + ) -> anyhow::Result { + let profile_info = profile_info.into(); + let api_client = self + .inner + .api_clients + .get(profile_info.member_id.user_name().domain())?; + + let GetUserProfileResponse { + encrypted_user_profile, + } = api_client + .as_get_user_profile(profile_info.member_id) + .await?; + + let user_profile = + UserProfile::decrypt(&profile_info.user_profile_key, &encrypted_user_profile)?; + + Ok(user_profile) + } +} diff --git a/coreclient/src/contacts/mod.rs b/coreclient/src/contacts/mod.rs index cf7996fcc..683b99e82 100644 --- a/coreclient/src/contacts/mod.rs +++ b/coreclient/src/contacts/mod.rs @@ -5,6 +5,7 @@ use openmls::{prelude::KeyPackage, versions::ProtocolVersion}; use openmls_rust_crypto::RustCrypto; use phnxtypes::{ + LibraryError, credentials::pseudonymous_credentials::PseudonymousCredential, crypto::{ ear::keys::{ @@ -22,6 +23,7 @@ use crate::{ ConversationId, clients::{api_clients::ApiClients, connection_establishment::FriendshipPackage}, groups::client_auth_info::StorableClientCredential, + key_stores::indexed_keys::{UserProfileKey, UserProfileKeyIndex}, }; use anyhow::Result; @@ -36,6 +38,7 @@ pub struct Contact { pub(crate) friendship_token: FriendshipToken, pub(crate) key_package_ear_key: KeyPackageEarKey, pub(crate) connection_key: ConnectionKey, + pub(crate) user_profile_key_index: UserProfileKeyIndex, // ID of the connection conversation with this contact. pub(crate) conversation_id: ConversationId, } @@ -44,6 +47,7 @@ pub struct Contact { pub(crate) struct ContactAddInfos { pub key_package: KeyPackage, pub identity_link_key: IdentityLinkKey, + pub user_profile_key: UserProfileKey, } impl Contact { @@ -51,8 +55,12 @@ impl Contact { client_id: AsClientId, conversation_id: ConversationId, friendship_package: FriendshipPackage, - ) -> Self { - Self { + ) -> Result<(Self, UserProfileKey), LibraryError> { + let user_profile_key = UserProfileKey::from_base_secret( + friendship_package.user_profile_base_secret, + client_id.user_name(), + )?; + let contact = Self { user_name: client_id.user_name().clone(), clients: vec![client_id], wai_ear_key: friendship_package.wai_ear_key, @@ -60,7 +68,9 @@ impl Contact { key_package_ear_key: friendship_package.key_package_ear_key, connection_key: friendship_package.connection_key, conversation_id, - } + user_profile_key_index: user_profile_key.index().clone(), + }; + Ok((contact, user_profile_key)) } /// Get the user name of this contact. @@ -109,9 +119,11 @@ impl Contact { if current_client_credential.fingerprint() != incoming_client_credential.fingerprint() { anyhow::bail!("Client credential does not match"); } + let user_profile_key = UserProfileKey::load(pool, &self.user_profile_key_index).await?; let add_info = ContactAddInfos { key_package: verified_key_package, identity_link_key, + user_profile_key, }; Ok(add_info) } diff --git a/coreclient/src/contacts/persistence.rs b/coreclient/src/contacts/persistence.rs index 2ddbc6253..6ad6d4e96 100644 --- a/coreclient/src/contacts/persistence.rs +++ b/coreclient/src/contacts/persistence.rs @@ -11,13 +11,15 @@ use phnxtypes::{ messages::FriendshipToken, }; use sqlx::{ - Database, Decode, Executor, Sqlite, SqliteExecutor, SqlitePool, error::BoxDynError, - prelude::Type, query, query_as, + Database, Decode, Sqlite, SqliteExecutor, SqlitePool, error::BoxDynError, prelude::Type, query, + query_as, }; use tokio_stream::StreamExt; use crate::{ - Contact, ConversationId, PartialContact, clients::connection_establishment::FriendshipPackage, + Contact, ConversationId, PartialContact, UserProfile, + clients::connection_establishment::FriendshipPackage, + key_stores::indexed_keys::{UserProfileKey, UserProfileKeyIndex}, store::StoreNotifier, }; @@ -49,6 +51,7 @@ struct SqlContact { friendship_token: FriendshipToken, key_package_ear_key: KeyPackageEarKey, connection_key: ConnectionKey, + user_profile_key_index: UserProfileKeyIndex, } impl From for Contact { @@ -61,6 +64,7 @@ impl From for Contact { conversation_id, key_package_ear_key, connection_key, + user_profile_key_index, }: SqlContact, ) -> Self { Self { @@ -71,6 +75,7 @@ impl From for Contact { key_package_ear_key, connection_key, conversation_id, + user_profile_key_index, } } } @@ -89,7 +94,8 @@ impl Contact { wai_ear_key AS "wai_ear_key: _", friendship_token AS "friendship_token: _", key_package_ear_key AS "key_package_ear_key: _", - connection_key AS "connection_key: _" + connection_key AS "connection_key: _", + user_profile_key_index AS "user_profile_key_index: _" FROM contacts WHERE user_name = ?"#, user_name ) @@ -108,7 +114,8 @@ impl Contact { wai_ear_key AS "wai_ear_key: _", friendship_token AS "friendship_token: _", key_package_ear_key AS "key_package_ear_key: _", - connection_key AS "connection_key: _" + connection_key AS "connection_key: _", + user_profile_key_index AS "user_profile_key_index: _" FROM contacts"# ) .fetch(executor) @@ -119,7 +126,7 @@ impl Contact { pub(crate) async fn store( &self, - executor: impl Executor<'_, Database = Sqlite>, + executor: impl SqliteExecutor<'_>, notifier: &mut StoreNotifier, ) -> sqlx::Result<()> { // TODO: Avoid creating Strings and collecting into a Vec. @@ -132,8 +139,8 @@ impl Contact { query!( "INSERT INTO contacts (user_name, conversation_id, clients, wai_ear_key, friendship_token, - key_package_ear_key, connection_key) - VALUES (?, ?, ?, ?, ?, ?, ?)", + key_package_ear_key, connection_key, user_profile_key_index) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)", self.user_name, self.conversation_id, clients_str, @@ -141,6 +148,7 @@ impl Contact { self.friendship_token, self.key_package_ear_key, self.connection_key, + self.user_profile_key_index, ) .execute(executor) .await?; @@ -226,13 +234,12 @@ impl PartialContact { notifier: &mut StoreNotifier, friendship_package: FriendshipPackage, client: AsClientId, - ) -> sqlx::Result { - let mut transaction = pool.begin().await?; - + user_profile: &UserProfile, + user_profile_key: &UserProfileKey, + ) -> anyhow::Result { let user_name = self.user_name.clone(); let conversation_id = self.conversation_id; - self.delete(&mut *transaction, notifier).await?; let contact = Contact { user_name, conversation_id, @@ -241,7 +248,14 @@ impl PartialContact { friendship_token: friendship_package.friendship_token, key_package_ear_key: friendship_package.key_package_ear_key, connection_key: friendship_package.connection_key, + user_profile_key_index: user_profile_key.index().clone(), }; + + let mut transaction = pool.begin().await?; + + self.delete(&mut *transaction, notifier).await?; + user_profile.upsert(&mut *transaction, notifier).await?; + user_profile_key.store(&mut *transaction).await?; contact.store(&mut *transaction, notifier).await?; transaction.commit().await?; @@ -261,16 +275,15 @@ mod tests { use sqlx::SqlitePool; use uuid::Uuid; - use crate::{ - ConversationId, UserProfile, conversations::persistence::tests::test_conversation, - }; + use crate::{ConversationId, conversations::persistence::tests::test_conversation}; use super::*; - fn test_contact(conversation_id: ConversationId) -> Contact { + fn test_contact(conversation_id: ConversationId) -> (Contact, UserProfileKey) { let user_id = Uuid::new_v4(); let user_name: QualifiedUserName = format!("{user_id}@localhost").parse().unwrap(); - Contact { + let user_profile_key = UserProfileKey::random(&user_name).unwrap(); + let contact = Contact { user_name: user_name.clone(), clients: vec![AsClientId::new(user_name, user_id)], wai_ear_key: WelcomeAttributionInfoEarKey::random().unwrap(), @@ -278,7 +291,9 @@ mod tests { key_package_ear_key: KeyPackageEarKey::random().unwrap(), connection_key: ConnectionKey::random().unwrap(), conversation_id, - } + user_profile_key_index: user_profile_key.index().clone(), + }; + (contact, user_profile_key) } fn test_partial_contact(conversation_id: ConversationId) -> PartialContact { @@ -298,7 +313,8 @@ mod tests { let conversation = test_conversation(); conversation.store(&pool, &mut store_notifier).await?; - let contact = test_contact(conversation.id()); + let (contact, user_profile_key) = test_contact(conversation.id()); + user_profile_key.store(&pool).await?; contact.store(&pool, &mut store_notifier).await?; let loaded = Contact::load(&pool, &contact.user_name).await?.unwrap(); @@ -354,6 +370,9 @@ mod tests { let partial = test_partial_contact(conversation.id()); let user_name = partial.user_name.clone(); + let user_profile_key = UserProfileKey::random(&user_name).unwrap(); + user_profile_key.store(&pool).await?; + partial.store(&pool, &mut store_notifier).await?; let friendship_package = FriendshipPackage { @@ -361,7 +380,7 @@ mod tests { key_package_ear_key: KeyPackageEarKey::random().unwrap(), connection_key: ConnectionKey::random().unwrap(), wai_ear_key: WelcomeAttributionInfoEarKey::random().unwrap(), - user_profile: UserProfile::new(user_name.clone(), None, None), + user_profile_base_secret: user_profile_key.base_secret().clone(), }; let contact = partial .mark_as_complete( @@ -369,6 +388,8 @@ mod tests { &mut store_notifier, friendship_package, AsClientId::new(user_name.clone(), Uuid::new_v4()), + &UserProfile::new(user_name.clone(), None, None), + &user_profile_key, ) .await?; diff --git a/coreclient/src/groups/mod.rs b/coreclient/src/groups/mod.rs index bd6d77d74..4bba393c1 100644 --- a/coreclient/src/groups/mod.rs +++ b/coreclient/src/groups/mod.rs @@ -28,8 +28,8 @@ use phnxtypes::{ ear::{ EarDecryptable, EarEncryptable, keys::{ - EncryptedIdentityLinkKey, GroupStateEarKey, IdentityLinkKey, - IdentityLinkWrapperKey, WelcomeAttributionInfoEarKey, + EncryptedIdentityLinkKey, EncryptedUserProfileKey, GroupStateEarKey, + IdentityLinkKey, IdentityLinkWrapperKey, WelcomeAttributionInfoEarKey, }, }, hpke::{HpkeDecryptable, JoinerInfoDecryptionKey}, @@ -57,8 +57,11 @@ use sqlx::{SqliteExecutor, SqlitePool}; use tracing::{debug, error}; use crate::{ - SystemMessage, clients::api_clients::ApiClients, contacts::ContactAddInfos, - conversations::messages::TimestampedMessage, key_stores::leaf_keys::LeafKeys, + SystemMessage, + clients::api_clients::ApiClients, + contacts::ContactAddInfos, + conversations::messages::TimestampedMessage, + key_stores::{indexed_keys::UserProfileKey, leaf_keys::LeafKeys}, }; use std::collections::HashSet; @@ -129,17 +132,36 @@ pub(crate) struct PartialCreateGroupParams { } impl PartialCreateGroupParams { - pub(crate) fn into_params(self, client_reference: QsReference) -> CreateGroupParamsOut { + pub(crate) fn into_params( + self, + client_reference: QsReference, + encrypted_user_profile_key: EncryptedUserProfileKey, + ) -> CreateGroupParamsOut { CreateGroupParamsOut { group_id: self.group_id, ratchet_tree: self.ratchet_tree, encrypted_identity_link_key: self.encrypted_identity_link_key, + encrypted_user_profile_key, creator_client_reference: client_reference, group_info: self.group_info, } } } +pub(super) struct ProfileInfo { + pub(super) user_profile_key: UserProfileKey, + pub(super) member_id: AsClientId, +} + +impl From<(AsClientId, UserProfileKey)> for ProfileInfo { + fn from((member_id, user_profile_key): (AsClientId, UserProfileKey)) -> Self { + Self { + member_id, + user_profile_key, + } + } +} + pub(crate) struct GroupData { bytes: Vec, } @@ -260,7 +282,7 @@ impl Group { welcome_attribution_info_ear_key: &WelcomeAttributionInfoEarKey, pool: &SqlitePool, api_clients: &ApiClients, - ) -> Result { + ) -> Result<(Self, Vec)> { let serialized_welcome = welcome_bundle.welcome.tls_serialize_detached()?; let mls_group_config = Self::default_mls_group_join_config(); @@ -365,7 +387,7 @@ impl Group { // Phase 3: Decrypt and verify the infra credentials. { let mut connection = pool.acquire().await?; - for client_auth_info in client_information { + for client_auth_info in &client_information { client_auth_info.store(&mut connection).await?; } } @@ -378,6 +400,27 @@ impl Group { let leaf_signer = leaf_keys.into_leaf_signer(); + let member_profile_info = joiner_info + .encrypted_user_profile_keys + .into_iter() + .zip( + client_information + .iter() + .map(|client_auth_info| client_auth_info.client_credential().identity()), + ) + .map(|(eupk, ci)| { + UserProfileKey::decrypt( + welcome_attribution_info.identity_link_wrapper_key(), + &eupk, + ci.user_name(), + ) + .map(|user_profile_key| ProfileInfo { + user_profile_key, + member_id: ci.clone(), + }) + }) + .collect::, _>>()?; + let group = Self { group_id: mls_group.group_id().clone(), mls_group, @@ -387,7 +430,7 @@ impl Group { pending_diff: None, }; - Ok(group) + Ok((group, member_profile_info)) } /// Join a group using an external commit. @@ -402,7 +445,7 @@ impl Group { identity_link_wrapper_key: IdentityLinkWrapperKey, aad: InfraAadMessage, own_client_credential: &ClientCredential, - ) -> Result<(Self, MlsMessageOut, MlsMessageOut)> { + ) -> Result<(Self, MlsMessageOut, MlsMessageOut, Vec)> { // TODO: We set the ratchet tree extension for now, as it is the only // way to make OpenMLS return a GroupInfo. This should change in the // future. @@ -415,6 +458,7 @@ impl Group { verifiable_group_info, ratchet_tree_in, encrypted_identity_link_keys, + encrypted_user_profile_keys, } = external_commit_info; // Let's create the group first so that we can access the GroupId. @@ -478,6 +522,27 @@ impl Group { } } + let member_profile_info = encrypted_user_profile_keys + .into_iter() + .zip( + client_information + .iter() + .map(|client_auth_info| client_auth_info.client_credential().identity()), + ) + .map(|(eupk, ci)| { + UserProfileKey::decrypt(&identity_link_wrapper_key, &eupk, ci.user_name()).map( + |user_profile_key| ProfileInfo { + user_profile_key, + member_id: ci.clone(), + }, + ) + }) + .collect::, _>>()?; + + // Phase 4: Fetch user profiles + + // TODO: Fetch user profiles + let group = Self { group_id: mls_group.group_id().clone(), mls_group, @@ -487,7 +552,7 @@ impl Group { pending_diff: None, }; - Ok((group, commit, group_info.into())) + Ok((group, commit, group_info.into(), member_profile_info)) } /// Invite the given list of contacts to join the group. @@ -507,19 +572,29 @@ impl Group { debug_assert!(add_infos.len() == client_credentials.len()); // Prepare KeyPackages - let (key_packages, identity_link_keys): (Vec, Vec) = add_infos - .into_iter() - .map(|ai| (ai.key_package, ai.identity_link_key)) - .unzip(); + let (key_packages, keys): (Vec, Vec<(IdentityLinkKey, UserProfileKey)>) = + add_infos + .into_iter() + .map(|ai| (ai.key_package, (ai.identity_link_key, ai.user_profile_key))) + .unzip(); + + let (identity_link_keys, user_profile_keys): (Vec, Vec) = + keys.into_iter().unzip(); let new_encrypted_identity_link_keys = identity_link_keys .iter() .map(|ilk| ilk.encrypt(&self.identity_link_wrapper_key)) .collect::, _>>()?; + let new_encrypted_user_profile_keys = user_profile_keys + .iter() + .map(|upk| upk.encrypt(&self.identity_link_wrapper_key)) + .collect::, _>>()?; + let aad_message: InfraAadMessage = InfraAadPayload::GroupOperation(GroupOperationParamsAad { new_encrypted_identity_link_keys, + new_encrypted_user_profile_keys, credential_update_option: None, }) .into(); @@ -615,6 +690,7 @@ impl Group { let remove_indices = GroupMembership::client_indices(&mut *connection, self.group_id(), &members).await?; let aad_payload = InfraAadPayload::GroupOperation(GroupOperationParamsAad { + new_encrypted_user_profile_keys: vec![], new_encrypted_identity_link_keys: vec![], credential_update_option: None, }); diff --git a/coreclient/src/key_stores/indexed_keys.rs b/coreclient/src/key_stores/indexed_keys.rs new file mode 100644 index 000000000..e539c68cc --- /dev/null +++ b/coreclient/src/key_stores/indexed_keys.rs @@ -0,0 +1,387 @@ +// SPDX-FileCopyrightText: 2025 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::marker::PhantomData; + +use phnxtypes::{ + LibraryError, + crypto::{ + ear::{ + AEAD_KEY_SIZE, EarDecryptable, EarEncryptable, EarKey, + keys::{EncryptedUserProfileKey, IdentityLinkWrapperKey}, + }, + errors::{DecryptionError, EncryptionError, RandomnessError}, + kdf::{KDF_KEY_SIZE, KdfDerivable, KdfKey}, + secrets::Secret, + }, + identifiers::QualifiedUserName, +}; +use serde::{Deserialize, Serialize}; +use sqlx::{ + Connection, Database, Decode, Encode, Sqlite, Type, encode::IsNull, error::BoxDynError, + sqlite::SqliteTypeInfo, +}; +use tls_codec::{TlsDeserializeBytes, TlsSerialize, TlsSize}; +use tracing::error; + +// The `LABEL` constant is used to identify the key type in the database. +pub(crate) trait KeyType { + type DerivationContext<'a>: tls_codec::Serialize + Clone; + + const LABEL: &'static str; +} + +#[allow(dead_code)] +pub(crate) trait Deletable: KeyType {} + +// Dummy wrapper type to avoid orphan problem. +struct KeyTypeInstance(PhantomData); + +impl<'q, KT: KeyType> Encode<'q, Sqlite> for KeyTypeInstance { + fn encode_by_ref( + &self, + buf: &mut ::ArgumentBuffer<'q>, + ) -> Result { + <&str as Encode>::encode_by_ref(&KT::LABEL, buf) + } +} + +impl<'r, KT: KeyType> Decode<'r, Sqlite> for KeyTypeInstance { + fn decode(value: ::ValueRef<'r>) -> Result { + let label: &str = Decode::::decode(value)?; + if label != KT::LABEL { + return Err(BoxDynError::from(format!( + "Invalid key type label: expected {}, got {}", + KT::LABEL, + label + ))); + } + Ok(Self(PhantomData)) + } +} + +impl Type for KeyTypeInstance { + fn type_info() -> SqliteTypeInfo { + <&str as Type>::type_info() + } +} + +impl KeyTypeInstance { + fn new() -> Self { + Self(PhantomData) + } +} + +impl tls_codec::Size for KeyTypeInstance { + fn tls_serialized_len(&self) -> usize { + KT::LABEL.len() + } +} + +impl tls_codec::Serialize for KeyTypeInstance { + fn tls_serialize(&self, writer: &mut W) -> Result { + let label = KT::LABEL.as_bytes(); + let written = writer.write(label)?; + Ok(written) + } +} + +#[derive( + Serialize, Deserialize, Clone, Debug, PartialEq, Eq, TlsDeserializeBytes, TlsSerialize, TlsSize, +)] +pub(crate) struct TypedSecret { + value: Secret, + _type: PhantomData<(KT, ST)>, +} + +impl<'q, KT, ST, const SIZE: usize> Encode<'q, Sqlite> for TypedSecret { + fn encode_by_ref( + &self, + buf: &mut ::ArgumentBuffer<'q>, + ) -> Result { + self.value.encode_by_ref(buf) + } +} + +impl Type for TypedSecret { + fn type_info() -> SqliteTypeInfo { + Secret::::type_info() + } +} + +impl<'r, KT, ST, const SIZE: usize> Decode<'r, Sqlite> for TypedSecret { + fn decode(value: ::ValueRef<'r>) -> Result { + let secret: Secret = Decode::::decode(value)?; + Ok(Self { + value: secret, + _type: PhantomData, + }) + } +} + +#[derive( + Serialize, Deserialize, Clone, Debug, PartialEq, Eq, TlsDeserializeBytes, TlsSerialize, TlsSize, +)] +pub(crate) struct BaseSecretType; +#[derive( + Serialize, Deserialize, Clone, Debug, PartialEq, Eq, TlsDeserializeBytes, TlsSerialize, TlsSize, +)] +struct KeySecretType; +#[derive( + Serialize, Deserialize, Clone, Debug, PartialEq, Eq, TlsDeserializeBytes, TlsSerialize, TlsSize, +)] +pub(crate) struct IndexSecretType; + +pub(crate) type BaseSecret = TypedSecret; +type Key = TypedSecret; +pub(crate) type Index = TypedSecret; + +impl BaseSecret { + pub fn random() -> Result { + let value = Secret::::random()?; + Ok(Self { + value, + _type: PhantomData, + }) + } +} + +impl AsRef> for BaseSecret { + fn as_ref(&self) -> &Secret { + &self.value + } +} + +impl KdfKey for BaseSecret { + const ADDITIONAL_LABEL: &'static str = "indexed key base secret"; +} + +impl From> for TypedSecret { + fn from(value: Secret) -> Self { + Self { + value, + _type: PhantomData, + } + } +} + +impl TypedSecret {} + +#[derive(TlsSerialize, TlsSize)] +struct DerivationContext<'a, KT: KeyType> { + context: KT::DerivationContext<'a>, + key_type_instance: KeyTypeInstance, +} + +impl KdfDerivable, DerivationContext<'_, KT>, AEAD_KEY_SIZE> + for Key +{ + const LABEL: &'static str = "key"; +} + +impl KdfDerivable, DerivationContext<'_, KT>, AEAD_KEY_SIZE> + for Index +{ + const LABEL: &'static str = "index"; +} + +#[derive( + Serialize, Deserialize, Clone, Debug, PartialEq, Eq, TlsDeserializeBytes, TlsSerialize, TlsSize, +)] +pub(crate) struct IndexedAeadKey { + base_secret: BaseSecret, + key: Key, + index: Index, +} + +impl IndexedAeadKey { + pub(crate) fn from_base_secret( + base_secret: BaseSecret, + context: KT::DerivationContext<'_>, + ) -> Result { + let derive_context = DerivationContext { + context, + key_type_instance: KeyTypeInstance::::new(), + }; + let key = Key::derive(&base_secret, &derive_context)?; + let index = Index::derive(&base_secret, &derive_context)?; + Ok(Self { + base_secret, + key, + index, + }) + } + + pub(crate) fn base_secret(&self) -> &BaseSecret { + &self.base_secret + } + + pub(crate) fn index(&self) -> &Index { + &self.index + } +} + +impl AsRef> for IndexedAeadKey { + fn as_ref(&self) -> &Secret { + &self.key.value + } +} + +impl EarKey for IndexedAeadKey {} + +// User profile key + +#[derive( + Clone, Debug, Serialize, Deserialize, Eq, PartialEq, TlsDeserializeBytes, TlsSerialize, TlsSize, +)] +pub(crate) struct UserProfileKeyType; + +impl KeyType for UserProfileKeyType { + type DerivationContext<'a> = &'a QualifiedUserName; + + const LABEL: &'static str = "user_profile_key"; +} + +pub(crate) type UserProfileKeyIndex = Index; + +pub(crate) type UserProfileBaseSecret = BaseSecret; + +pub(crate) type UserProfileKey = IndexedAeadKey; + +impl UserProfileKey { + pub(crate) fn random(user_name: &QualifiedUserName) -> Result { + let base_secret = BaseSecret::random()?; + Self::from_base_secret(base_secret, user_name).map_err(|e| { + error!(error = %e, "Key derivation error"); + RandomnessError::InsufficientRandomness + }) + } + + pub(crate) fn encrypt( + &self, + wrapper_key: &IdentityLinkWrapperKey, + ) -> Result { + self.base_secret.encrypt(wrapper_key) + } + + pub(crate) fn decrypt( + wrapper_key: &IdentityLinkWrapperKey, + encrypted_key: &EncryptedUserProfileKey, + user_name: &QualifiedUserName, + ) -> Result { + let base_secret = BaseSecret::decrypt(wrapper_key, encrypted_key)?; + Self::from_base_secret(base_secret, user_name).map_err(|e| { + error!(error = %e, "Key derivation error"); + DecryptionError::DecryptionError + }) + } +} + +impl EarEncryptable for UserProfileBaseSecret {} +impl EarDecryptable for UserProfileBaseSecret {} + +mod persistence { + use sqlx::{SqliteConnection, SqliteExecutor, query, query_as}; + + use super::*; + + impl IndexedAeadKey { + pub(crate) async fn store( + &self, + connection: impl SqliteExecutor<'_>, + ) -> Result<(), sqlx::Error> { + query!( + "INSERT OR IGNORE INTO indexed_keys (base_secret, key_value, key_index) + VALUES ($1, $2, $3)", + self.base_secret, + self.key, + self.index + ) + .execute(connection) + .await?; + Ok(()) + } + + pub(crate) async fn store_own( + &self, + connection: &mut SqliteConnection, + ) -> Result<(), sqlx::Error> { + let key_type = KeyTypeInstance::::new(); + let mut transaction = connection.begin().await?; + query!( + "INSERT OR IGNORE INTO indexed_keys (base_secret, key_value, key_index) + VALUES ($1, $2, $3)", + self.base_secret, + self.key, + self.index + ) + .execute(&mut *transaction) + .await?; + query!( + "INSERT OR IGNORE INTO own_key_indices (key_index, key_type) VALUES ($1, $2)", + self.index, + key_type + ) + .execute(&mut *transaction) + .await?; + transaction.commit().await?; + Ok(()) + } + + pub(crate) async fn load( + connection: impl SqliteExecutor<'_>, + index: &UserProfileKeyIndex, + ) -> Result { + query_as!( + IndexedAeadKey, + r#" + SELECT + base_secret AS "base_secret: _", + key_value AS "key: _", + key_index AS "index: _" + FROM indexed_keys + WHERE key_index = ? + LIMIT 1"#, + index, + ) + .fetch_one(connection) + .await + } + + pub(crate) async fn load_own( + connection: impl SqliteExecutor<'_>, + ) -> Result { + let key_type = KeyTypeInstance::::new(); + query_as!( + IndexedAeadKey, + r#"SELECT + ik.key_index as "index: _", + ik.key_value as "key: _", + ik.base_secret as "base_secret: _" + FROM own_key_indices oki + JOIN indexed_keys ik ON oki.key_index = ik.key_index + WHERE oki.key_type = ?"#, + key_type + ) + .fetch_one(connection) + .await + } + } + + impl IndexedAeadKey { + #[allow(dead_code)] + pub(crate) async fn delete( + &self, + connection: impl SqliteExecutor<'_>, + ) -> Result<(), sqlx::Error> { + query!( + "DELETE FROM indexed_keys WHERE key_index = $1", + self.index.value + ) + .execute(connection) + .await?; + Ok(()) + } + } +} diff --git a/coreclient/src/key_stores/mod.rs b/coreclient/src/key_stores/mod.rs index 9a4a731a1..ed5756643 100644 --- a/coreclient/src/key_stores/mod.rs +++ b/coreclient/src/key_stores/mod.rs @@ -37,6 +37,7 @@ use phnxtypes::{ use serde::{Deserialize, Serialize}; pub(crate) mod as_credentials; +pub(crate) mod indexed_keys; pub(crate) mod leaf_keys; pub(crate) mod queue_ratchets; diff --git a/coreclient/src/user_profiles/mod.rs b/coreclient/src/user_profiles/mod.rs index 19774fa89..e36744c61 100644 --- a/coreclient/src/user_profiles/mod.rs +++ b/coreclient/src/user_profiles/mod.rs @@ -7,12 +7,18 @@ use std::fmt::Display; -use phnxtypes::identifiers::QualifiedUserName; +use phnxtypes::{ + crypto::ear::{EarDecryptable, EarEncryptable}, + identifiers::QualifiedUserName, + messages::client_as_out::EncryptedUserProfile, +}; use serde::{Deserialize, Serialize}; use sqlx::{Database, Decode, Encode, Sqlite, encode::IsNull, error::BoxDynError}; use thiserror::Error; use tls_codec::{TlsDeserializeBytes, TlsSerialize, TlsSize}; +use crate::key_stores::indexed_keys::UserProfileKey; + pub(crate) mod persistence; /// A user profile contains information about a user, such as their display name @@ -180,3 +186,6 @@ impl Asset { } } } + +impl EarEncryptable for UserProfile {} +impl EarDecryptable for UserProfile {} diff --git a/coreclient/src/user_profiles/persistence.rs b/coreclient/src/user_profiles/persistence.rs index eb4e61378..6216c1069 100644 --- a/coreclient/src/user_profiles/persistence.rs +++ b/coreclient/src/user_profiles/persistence.rs @@ -92,26 +92,6 @@ impl UserProfile { Ok(()) } - /// Stores this new [`UserProfile`] if one doesn't already exist. - pub(crate) async fn store_or_ignore( - &self, - executor: impl SqliteExecutor<'_>, - notifier: &mut StoreNotifier, - ) -> sqlx::Result<()> { - query!( - "INSERT OR IGNORE INTO users - (user_name, display_name, profile_picture) VALUES (?, ?, ?)", - self.user_name, - self.display_name_option, - self.profile_picture_option - ) - .execute(executor) - .await?; - // TODO: We can skip this notification if the user profile was already stored. - notifier.add(self.user_name.clone()); - Ok(()) - } - /// Update the user's display name and profile picture in the database. To store a new profile, /// use [`register_as_conversation_participant`] instead. pub(crate) async fn update( diff --git a/types/src/credentials/keys.rs b/types/src/credentials/keys.rs index 592b41d2e..21a8ac491 100644 --- a/types/src/credentials/keys.rs +++ b/types/src/credentials/keys.rs @@ -261,11 +261,10 @@ impl PseudonymousCredentialSigningKey { }; // Derive the identity link key based on the TBS - let identity_link_key = - IdentityLinkKey::derive(connection_key, tbs.clone()).map_err(|e| { - error!(%e, "Failed to derive identity link key"); - CredentialCreationError::KeyDerivationFailed - })?; + let identity_link_key = IdentityLinkKey::derive(connection_key, &tbs).map_err(|e| { + error!(%e, "Failed to derive identity link key"); + CredentialCreationError::KeyDerivationFailed + })?; // Sign the TBS and encrypt the identity link let signed_pseudonymous_credential = tbs.sign(client_signer).unwrap(); diff --git a/types/src/credentials/pseudonymous_credentials.rs b/types/src/credentials/pseudonymous_credentials.rs index a25d14481..52be7affb 100644 --- a/types/src/credentials/pseudonymous_credentials.rs +++ b/types/src/credentials/pseudonymous_credentials.rs @@ -97,7 +97,7 @@ impl PseudonymousCredential { connection_key: &ConnectionKey, ) -> Result<(PseudonymousCredentialPlaintext, IdentityLinkKey), IdentityLinkVerificationError> { - let identity_link_key = IdentityLinkKey::derive(connection_key, self.tbs.clone())?; + let identity_link_key = IdentityLinkKey::derive(connection_key, &self.tbs)?; let plaintext = self.decrypt_and_verify(&identity_link_key)?; Ok((plaintext, identity_link_key)) } diff --git a/types/src/crypto/ear/keys.rs b/types/src/crypto/ear/keys.rs index b1dd4c30c..3a662b721 100644 --- a/types/src/crypto/ear/keys.rs +++ b/types/src/crypto/ear/keys.rs @@ -487,3 +487,20 @@ impl From> for IdentityLinkWrapperKey { Self { key: secret } } } + +#[derive(Clone, Debug, Serialize, Deserialize, TlsSerialize, TlsSize, TlsDeserializeBytes)] +pub struct EncryptedUserProfileKey { + ciphertext: Ciphertext, +} + +impl From for EncryptedUserProfileKey { + fn from(ciphertext: Ciphertext) -> Self { + Self { ciphertext } + } +} + +impl AsRef for EncryptedUserProfileKey { + fn as_ref(&self) -> &Ciphertext { + &self.ciphertext + } +} diff --git a/types/src/crypto/ear/traits.rs b/types/src/crypto/ear/traits.rs index 96e47df21..c11d48f40 100644 --- a/types/src/crypto/ear/traits.rs +++ b/types/src/crypto/ear/traits.rs @@ -25,7 +25,7 @@ use super::{AEAD_KEY_SIZE, AEAD_NONCE_SIZE, Aead, Ciphertext}; /// A trait meant for structs holding a symmetric key of size [`AEAD_KEY_SIZE`]. /// It enables use of these keys for encryption and decryption operations. -pub trait EarKey: AsRef> + From> { +pub trait EarKey: AsRef> { // Encrypt the given plaintext under the given key. Generates a random nonce internally. #[instrument(level = "trace", skip_all, fields(key_type = std::any::type_name::()))] fn encrypt(&self, plaintext: &[u8]) -> Result { diff --git a/types/src/crypto/kdf/mod.rs b/types/src/crypto/kdf/mod.rs index b8334069a..cb80e3fa2 100644 --- a/types/src/crypto/kdf/mod.rs +++ b/types/src/crypto/kdf/mod.rs @@ -12,7 +12,7 @@ use hkdf::Hkdf; use super::Hash; -pub use traits::{KdfDerivable, KdfExtractable}; +pub use traits::{KdfDerivable, KdfExtractable, KdfKey}; /// This type determines the KDF used by the backend. pub type Kdf = Hkdf; diff --git a/types/src/crypto/kdf/traits.rs b/types/src/crypto/kdf/traits.rs index 2cbb2be9c..164beae27 100644 --- a/types/src/crypto/kdf/traits.rs +++ b/types/src/crypto/kdf/traits.rs @@ -14,7 +14,7 @@ use super::{KDF_KEY_SIZE, Kdf}; /// A trait that allows the use of a symmetric secret of size [`KDF_KEY_SIZE`] /// to derive additional key material. -pub trait KdfKey: AsRef> + From> { +pub trait KdfKey: AsRef> { /// Label used as additional input in all derivations made with this KDF key. const ADDITIONAL_LABEL: &'static str; @@ -35,22 +35,17 @@ pub trait KdfKey: AsRef> + From> { /// structs of type `AdditionalInfo` can be provided as context. [`Self::LABEL`] /// is used as label in the derivation. pub trait KdfDerivable< - DerivingKey: KdfKey + std::fmt::Debug, - AdditionalInfo: tls_codec::Serialize + std::fmt::Debug, + DerivingKey: KdfKey, + AdditionalInfo: tls_codec::Serialize, const OUTPUT_LENGTH: usize, ->: From> + std::fmt::Debug +>: From> { /// This label is appended to the info given in the derivation. const LABEL: &'static str; - #[instrument(level = "trace", ret, skip_all, fields( - kdf_key_type = std::any::type_name::(), - label = %Self::LABEL, - additional_info = ?additional_info, - ))] fn derive( kdf_key: &DerivingKey, - additional_info: AdditionalInfo, + additional_info: &AdditionalInfo, ) -> Result { let info = [ &additional_info diff --git a/types/src/crypto/ratchet/mod.rs b/types/src/crypto/ratchet/mod.rs index deda8ebc8..9e8a99148 100644 --- a/types/src/crypto/ratchet/mod.rs +++ b/types/src/crypto/ratchet/mod.rs @@ -59,7 +59,7 @@ impl> TryFrom type Error = LibraryError; fn try_from(secret: RatchetSecret) -> Result { - let key = RatchetKey::derive(&secret, Vec::new()).map_err(|_| LibraryError)?; + let key = RatchetKey::derive(&secret, &Vec::new()).map_err(|_| LibraryError)?; Ok(Self { sequence_number: 0, secret, @@ -82,9 +82,9 @@ impl> } fn ratchet_forward(&mut self) -> Result<(), EncryptionError> { - let secret = RatchetSecret::derive(&self.secret, Vec::new()) + let secret = RatchetSecret::derive(&self.secret, &Vec::new()) .map_err(|_| EncryptionError::SerializationError)?; - let key = RatchetKey::derive(&secret, Vec::new()) + let key = RatchetKey::derive(&secret, &Vec::new()) .map_err(|_| EncryptionError::SerializationError)?; self.secret = secret; diff --git a/types/src/crypto/secrets.rs b/types/src/crypto/secrets.rs index f4edfb44e..f06f18df9 100644 --- a/types/src/crypto/secrets.rs +++ b/types/src/crypto/secrets.rs @@ -45,7 +45,7 @@ impl Secret { } /// Generate a fresh, random secret. - pub(super) fn random() -> Result { + pub fn random() -> Result { let mut secret = [0; LENGTH]; // TODO: Use a proper rng provider. rand_chacha::ChaCha20Rng::from_entropy() diff --git a/types/src/errors/auth_service.rs b/types/src/errors/auth_service.rs index b201562e7..a16e82420 100644 --- a/types/src/errors/auth_service.rs +++ b/types/src/errors/auth_service.rs @@ -225,6 +225,26 @@ pub enum AsVerificationError { Api(#[from] VersionError), } +#[derive(Debug, Error)] +#[repr(u8)] +pub enum GetUserProfileError { + #[error("User not found")] + UserNotFound, + /// Storage provider error + #[error("Storage provider error")] + StorageError, +} + +#[derive(Error, Debug, Clone, TlsSerialize, TlsSize, TlsDeserializeBytes)] +#[repr(u8)] +pub enum UpdateUserProfileError { + #[error("User not found")] + UserNotFound, + /// Storage provider error + #[error("Storage provider error")] + StorageError, +} + #[derive(Debug, Error)] #[repr(u8)] pub enum AsProcessingError { @@ -264,4 +284,8 @@ pub enum AsProcessingError { Init2FactorAuthError(#[from] Init2FactorAuthError), #[error(transparent)] AsCredentialsError(#[from] AsCredentialsError), + #[error(transparent)] + GetUserProfileError(#[from] GetUserProfileError), + #[error(transparent)] + UpdateUserProfileError(#[from] UpdateUserProfileError), } diff --git a/types/src/identifiers/mod.rs b/types/src/identifiers/mod.rs index a1556c8f9..c643b7c19 100644 --- a/types/src/identifiers/mod.rs +++ b/types/src/identifiers/mod.rs @@ -10,7 +10,7 @@ use sqlx::{ Database, Decode, Encode, Postgres, Sqlite, Type, encode::IsNull, error::BoxDynError, postgres::PgValueRef, }; -use tls_codec_impls::{TlsString, TlsUuid}; +use tls_codec_impls::TlsUuid; use tracing::{debug, error}; use url::Host; use uuid::Uuid; @@ -25,6 +25,8 @@ use super::*; mod tls_codec_impls; +pub use tls_codec_impls::TlsString; + pub const QS_CLIENT_REFERENCE_EXTENSION_TYPE: u16 = 0xff00; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] diff --git a/types/src/identifiers/tls_codec_impls.rs b/types/src/identifiers/tls_codec_impls.rs index 8140fe817..ddf15f82f 100644 --- a/types/src/identifiers/tls_codec_impls.rs +++ b/types/src/identifiers/tls_codec_impls.rs @@ -75,7 +75,7 @@ impl Serialize for TlsStr<'_> { )] #[sqlx(transparent)] #[serde(transparent)] -pub(super) struct TlsString(pub String); +pub struct TlsString(pub String); impl fmt::Display for TlsString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/types/src/messages/client_as.rs b/types/src/messages/client_as.rs index be13d92da..963e3ba66 100644 --- a/types/src/messages/client_as.rs +++ b/types/src/messages/client_as.rs @@ -39,8 +39,9 @@ use crate::{ use super::{ ApiVersion, AsTokenType, EncryptedAsQueueMessage, MlsInfraVersion, client_as_out::{ - ConnectionPackageIn, FinishUserRegistrationParamsIn, FinishUserRegistrationParamsTbsIn, - VerifiableConnectionPackage, + ConnectionPackageIn, EncryptedUserProfile, FinishUserRegistrationParamsIn, + FinishUserRegistrationParamsTbsIn, GetUserProfileParams, UpdateUserProfileParams, + UpdateUserProfileParamsTbs, VerifiableConnectionPackage, }, }; @@ -268,6 +269,7 @@ pub struct FinishUserRegistrationParamsTbs { pub initial_ratchet_secret: RatchetSecret, pub connection_packages: Vec, pub opaque_registration_record: OpaqueRegistrationRecord, + pub encrypted_user_profile: EncryptedUserProfile, } impl Signable for FinishUserRegistrationParamsTbs { @@ -930,6 +932,8 @@ pub enum AsRequestParamsOut { EnqueueMessage(EnqueueMessageParams), AsCredentials(AsCredentialsParams), IssueTokens(IssueTokensParams), + GetUserProfile(GetUserProfileParams), + UpdateUserProfile(UpdateUserProfileParams), } #[derive(Debug, TlsSerialize, TlsSize)] @@ -944,13 +948,14 @@ pub enum VerifiedAsRequestParams { PublishConnectionPackages(AsPublishConnectionPackagesParamsTbs), ClientConnectionPackage(ClientConnectionPackageParamsTbs), IssueTokens(IssueTokensParamsTbs), - // Endpoints that don't require authentication UserConnectionPackages(UserConnectionPackagesParams), InitiateClientAddition(InitiateClientAdditionParams), UserClients(UserClientsParams), AsCredentials(AsCredentialsParams), EnqueueMessage(EnqueueMessageParams), InitUserRegistration(InitUserRegistrationParams), + GetUserProfile(GetUserProfileParams), + UpdateUserProfile(UpdateUserProfileParamsTbs), } #[derive(Debug)] @@ -992,6 +997,7 @@ impl Verifiable for ClientCredentialAuth { VerifiedAsRequestParams::FinishUserRegistration(params) => { params.tls_serialize_detached() } + VerifiedAsRequestParams::UpdateUserProfile(params) => params.tls_serialize_detached(), // All other endpoints aren't authenticated via client credential signatures. VerifiedAsRequestParams::DeleteUser(_) | VerifiedAsRequestParams::FinishClientAddition(_) @@ -1000,7 +1006,8 @@ impl Verifiable for ClientCredentialAuth { | VerifiedAsRequestParams::UserClients(_) | VerifiedAsRequestParams::AsCredentials(_) | VerifiedAsRequestParams::EnqueueMessage(_) - | VerifiedAsRequestParams::InitUserRegistration(_) => Ok(vec![]), + | VerifiedAsRequestParams::InitUserRegistration(_) + | VerifiedAsRequestParams::GetUserProfile(_) => Ok(vec![]), } } diff --git a/types/src/messages/client_as_out.rs b/types/src/messages/client_as_out.rs index 3e1363a28..782fb46cb 100644 --- a/types/src/messages/client_as_out.rs +++ b/types/src/messages/client_as_out.rs @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use tls_codec::{Serialize, TlsDeserializeBytes, TlsSerialize, TlsSize, TlsVarInt}; +use serde::{Deserialize, Serialize}; +use tls_codec::{Serialize as _, TlsDeserializeBytes, TlsSerialize, TlsSize, TlsVarInt}; use crate::{ credentials::{ @@ -11,10 +12,11 @@ use crate::{ }, crypto::{ ConnectionEncryptionKey, RatchetEncryptionKey, + ear::Ciphertext, kdf::keys::RatchetSecret, opaque::{OpaqueLoginResponse, OpaqueRegistrationRecord, OpaqueRegistrationResponse}, signatures::{ - signable::{Signature, Verifiable}, + signable::{Signable, Signature, SignedStruct, Verifiable}, traits::SignatureVerificationError, }, }, @@ -135,6 +137,7 @@ pub enum AsProcessResponseIn { UserClients(UserClientsResponseIn), AsCredentials(AsCredentialsResponseIn), InitUserRegistration(InitUserRegistrationResponseIn), + GetUserProfile(GetUserProfileResponse), } #[derive(Debug, TlsSerialize, TlsDeserializeBytes, TlsSize)] @@ -205,6 +208,7 @@ pub struct FinishUserRegistrationParamsTbsIn { pub initial_ratchet_secret: RatchetSecret, pub connection_packages: Vec, pub opaque_registration_record: OpaqueRegistrationRecord, + pub encrypted_user_profile: EncryptedUserProfile, } #[derive(Debug, TlsDeserializeBytes, TlsSize)] @@ -231,6 +235,105 @@ impl ClientCredentialAuthenticator for FinishUserRegistrationParamsIn { const LABEL: &'static str = "Finish User Registration Parameters"; } +#[derive(Debug, TlsSerialize, TlsDeserializeBytes, TlsSize)] +pub struct GetUserProfileParams { + pub client_id: AsClientId, +} + +impl NoAuth for GetUserProfileParams { + fn into_verified(self) -> VerifiedAsRequestParams { + VerifiedAsRequestParams::GetUserProfile(self) + } +} + +#[derive( + Debug, + Clone, + TlsSerialize, + TlsDeserializeBytes, + TlsSize, + PartialEq, + Eq, + sqlx::Type, + Serialize, + Deserialize, +)] +#[sqlx(transparent)] +pub struct EncryptedUserProfile(Ciphertext); + +impl From for EncryptedUserProfile { + fn from(ciphertext: Ciphertext) -> Self { + Self(ciphertext) + } +} + +impl AsRef for EncryptedUserProfile { + fn as_ref(&self) -> &Ciphertext { + &self.0 + } +} + +#[cfg(any(test, feature = "test_utils"))] +impl EncryptedUserProfile { + pub fn dummy() -> Self { + let ctxt = Ciphertext::dummy(); + Self(ctxt) + } +} + +#[derive(Debug, TlsSerialize, TlsDeserializeBytes, TlsSize)] +pub struct GetUserProfileResponse { + pub encrypted_user_profile: EncryptedUserProfile, +} + +#[derive(Debug, TlsSerialize, TlsDeserializeBytes, TlsSize)] +pub struct UpdateUserProfileParamsTbs { + pub client_id: AsClientId, + pub user_profile: EncryptedUserProfile, +} + +#[derive(Debug, TlsSerialize, TlsDeserializeBytes, TlsSize)] +pub struct UpdateUserProfileParams { + payload: UpdateUserProfileParamsTbs, + signature: Signature, +} + +impl ClientCredentialAuthenticator for UpdateUserProfileParams { + type Tbs = FinishUserRegistrationParamsTbsIn; + + fn client_id(&self) -> AsClientId { + self.payload.client_id.clone() + } + + fn into_payload(self) -> VerifiedAsRequestParams { + VerifiedAsRequestParams::UpdateUserProfile(self.payload) + } + + fn signature(&self) -> &Signature { + &self.signature + } + + const LABEL: &'static str = "Finish User Registration Parameters"; +} + +impl Signable for UpdateUserProfileParamsTbs { + type SignedOutput = UpdateUserProfileParams; + + fn unsigned_payload(&self) -> Result, tls_codec::Error> { + self.tls_serialize_detached() + } + + fn label(&self) -> &str { + UpdateUserProfileParams::LABEL + } +} + +impl SignedStruct for UpdateUserProfileParams { + fn from_payload(payload: UpdateUserProfileParamsTbs, signature: Signature) -> Self { + Self { payload, signature } + } +} + #[derive(Debug, TlsDeserializeBytes, TlsSize)] pub struct ClientToAsMessageIn { // This essentially includes the wire format. @@ -317,6 +420,8 @@ pub enum AsRequestParamsIn { EnqueueMessage(EnqueueMessageParams), AsCredentials(AsCredentialsParams), IssueTokens(IssueTokensParams), + GetUserProfile(GetUserProfileParams), + UpdateUserProfile(UpdateUserProfileParams), } impl AsRequestParamsIn { @@ -349,6 +454,9 @@ impl AsRequestParamsIn { Self::IssueTokens(params) => { AsAuthMethod::ClientCredential(params.credential_auth_info()) } + Self::UpdateUserProfile(params) => { + AsAuthMethod::ClientCredential(params.credential_auth_info()) + } // We verify user registration finish requests like a // ClientCredentialAuth request and then additionally complete the // OPAQUE registration afterwards. @@ -362,6 +470,7 @@ impl AsRequestParamsIn { Self::InitUserRegistration(params) => AsAuthMethod::None(params.into_verified()), Self::InitiateClientAddition(params) => AsAuthMethod::None(params.into_verified()), Self::AsCredentials(params) => AsAuthMethod::None(params.into_verified()), + Self::GetUserProfile(params) => AsAuthMethod::None(params.into_verified()), } } } diff --git a/types/src/messages/client_ds.rs b/types/src/messages/client_ds.rs index 276076d0b..e2b48f371 100644 --- a/types/src/messages/client_ds.rs +++ b/types/src/messages/client_ds.rs @@ -28,7 +28,9 @@ use crate::{ crypto::{ ear::{ EarDecryptable, EarEncryptable, GenericDeserializable, GenericSerializable, - keys::{EncryptedIdentityLinkKey, GroupStateEarKey, RatchetKey}, + keys::{ + EncryptedIdentityLinkKey, EncryptedUserProfileKey, GroupStateEarKey, RatchetKey, + }, }, hpke::{ HpkeDecryptable, HpkeEncryptable, JoinerInfoDecryptionKey, JoinerInfoEncryptionKey, @@ -220,6 +222,7 @@ pub struct CreateGroupParams { pub group_id: GroupId, pub leaf_node: RatchetTreeIn, pub encrypted_identity_link_key: EncryptedIdentityLinkKey, + pub encrypted_user_profile_key: EncryptedUserProfileKey, pub creator_qs_reference: QsReference, pub group_info: MlsMessageIn, } @@ -268,6 +271,7 @@ pub struct CredentialUpdate { #[derive(TlsSerialize, TlsDeserializeBytes, TlsSize)] pub struct GroupOperationParamsAad { pub new_encrypted_identity_link_keys: Vec, + pub new_encrypted_user_profile_keys: Vec, pub credential_update_option: Option, } @@ -291,6 +295,7 @@ pub struct JoinConnectionGroupParams { pub struct JoinConnectionGroupParamsAad { pub encrypted_identity_link_key: EncryptedIdentityLinkKey, pub encrypted_friendship_package: EncryptedFriendshipPackage, + pub encrypted_user_profile_key: EncryptedUserProfileKey, } #[derive(Debug, TlsDeserializeBytes, TlsSize)] @@ -653,6 +658,7 @@ impl VerifiedStruct for DsVersionedRequestParams { pub struct DsJoinerInformation { pub group_state_ear_key: GroupStateEarKey, pub encrypted_identity_link_keys: Vec, + pub encrypted_user_profile_keys: Vec, pub ratchet_tree: RatchetTree, } @@ -690,6 +696,7 @@ impl HpkeEncryptable pub struct DsJoinerInformationIn { pub group_state_ear_key: GroupStateEarKey, pub encrypted_identity_link_keys: Vec, + pub encrypted_user_profile_keys: Vec, pub ratchet_tree: RatchetTreeIn, } diff --git a/types/src/messages/client_ds_out.rs b/types/src/messages/client_ds_out.rs index 687cbae97..7cfaebaeb 100644 --- a/types/src/messages/client_ds_out.rs +++ b/types/src/messages/client_ds_out.rs @@ -20,7 +20,7 @@ use tls_codec::{Serialize, TlsDeserializeBytes, TlsSerialize, TlsSize, TlsVarInt use crate::{ crypto::{ - ear::keys::{EncryptedIdentityLinkKey, GroupStateEarKey}, + ear::keys::{EncryptedIdentityLinkKey, EncryptedUserProfileKey, GroupStateEarKey}, signatures::signable::{Signable, Signature, SignedStruct}, }, errors::version::VersionError, @@ -42,6 +42,7 @@ pub struct ExternalCommitInfoIn { pub verifiable_group_info: VerifiableGroupInfo, pub ratchet_tree_in: RatchetTreeIn, pub encrypted_identity_link_keys: Vec, + pub encrypted_user_profile_keys: Vec, } #[expect(clippy::large_enum_variant)] @@ -90,13 +91,19 @@ impl tls_codec::DeserializeBytes for DsVersionedProcessResponseIn { } } -#[expect(clippy::large_enum_variant)] +#[derive(Debug, TlsDeserializeBytes, TlsSize)] +pub struct WelcomeInfoIn { + pub ratchet_tree: RatchetTreeIn, + pub encrypted_identity_link_keys: Vec, + pub encrypted_user_profile_keys: Vec, +} + #[derive(TlsDeserializeBytes, TlsSize)] #[repr(u8)] pub enum DsProcessResponseIn { Ok, FanoutTimestamp(TimeStamp), - WelcomeInfo(RatchetTreeIn), + WelcomeInfo(WelcomeInfoIn), ExternalCommitInfo(ExternalCommitInfoIn), GroupId(GroupId), } @@ -106,6 +113,7 @@ pub struct CreateGroupParamsOut { pub group_id: GroupId, pub ratchet_tree: RatchetTree, pub encrypted_identity_link_key: EncryptedIdentityLinkKey, + pub encrypted_user_profile_key: EncryptedUserProfileKey, pub creator_client_reference: QsReference, pub group_info: MlsMessageOut, }