From 50e989466702f72e8e47792be0ee82e1058a7288 Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Fri, 13 Dec 2024 08:52:33 +0000 Subject: [PATCH 01/12] switch default ciphersuite to 2 --- charts/galley/values.yaml | 4 +- .../src/developer/reference/config-options.md | 8 +- integration/test/MLS/Util.hs | 2 +- integration/test/Test/FeatureFlags/Util.hs | 4 +- integration/test/Test/MLS.hs | 4 +- integration/test/Test/MLS/KeyPackage.hs | 98 ++++++++++--------- integration/test/Test/MLS/One2One.hs | 2 +- integration/test/Testlib/Types.hs | 2 +- ...ject_GetOne2OneConversationResponseOk.json | 2 +- .../src/Wire/API/Conversation/Protocol.hs | 2 +- libs/wire-api/src/Wire/API/MLS/CipherSuite.hs | 9 +- libs/wire-api/src/Wire/API/Team/Feature.hs | 6 +- .../testObject_Conversation_v2_user_4.json | 2 +- .../testObject_Conversation_v5_user_4.json | 2 +- ...testObject_PublicSubConversation_v5_2.json | 2 +- services/brig/src/Brig/API/MLS/CipherSuite.hs | 4 +- 16 files changed, 83 insertions(+), 70 deletions(-) diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 60122db2c23..45d4cbc94a5 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -115,8 +115,8 @@ config: config: protocolToggleUsers: [] defaultProtocol: proteus - allowedCipherSuites: [1] - defaultCipherSuite: 1 + allowedCipherSuites: [2] + defaultCipherSuite: 2 supportedProtocols: [proteus, mls] # must contain defaultProtocol lockStatus: unlocked searchVisibilityInbound: diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index f2108f44239..3c0adf4a55c 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -300,8 +300,8 @@ mls: protocolToggleUsers: [] defaultProtocol: mls supportedProtocols: [proteus, mls] # must contain defaultProtocol - allowedCipherSuites: [1] - defaultCipherSuite: 1 + allowedCipherSuites: [2] + defaultCipherSuite: 2 lockStatus: locked ``` @@ -316,8 +316,8 @@ mls: protocolToggleUsers: [] defaultProtocol: mls supportedProtocols: [proteus, mls] # must contain defaultProtocol - allowedCipherSuites: [1] - defaultCipherSuite: 1 + allowedCipherSuites: [2] + defaultCipherSuite: 2 ``` ### MLS End-to-End Identity diff --git a/integration/test/MLS/Util.hs b/integration/test/MLS/Util.hs index e70fa74d259..68e9fcb3a12 100644 --- a/integration/test/MLS/Util.hs +++ b/integration/test/MLS/Util.hs @@ -90,7 +90,7 @@ mlscli mConvId cs cid args mbstdin = do liftIO (createDirectory (bd cid2Str cid)) `catch` \e -> if (isAlreadyExistsError e) - then assertFailure "client directory for mls state already exists" + then pure () -- creates a file per signature scheme else throwM e -- initialise new keystore diff --git a/integration/test/Test/FeatureFlags/Util.hs b/integration/test/Test/FeatureFlags/Util.hs index bca97070383..551c02a962b 100644 --- a/integration/test/Test/FeatureFlags/Util.hs +++ b/integration/test/Test/FeatureFlags/Util.hs @@ -95,8 +95,8 @@ defAllFeatures = [ "protocolToggleUsers" .= ([] :: [String]), "defaultProtocol" .= "proteus", "supportedProtocols" .= ["proteus", "mls"], - "allowedCipherSuites" .= ([1] :: [Int]), - "defaultCipherSuite" .= A.Number 1 + "allowedCipherSuites" .= ([2] :: [Int]), + "defaultCipherSuite" .= A.Number 2 ] ], "searchVisibilityInbound" .= disabled, diff --git a/integration/test/Test/MLS.hs b/integration/test/Test/MLS.hs index 856f480e983..7c1ab6e88ab 100644 --- a/integration/test/Test/MLS.hs +++ b/integration/test/Test/MLS.hs @@ -390,7 +390,7 @@ testAddUserSimple suite ctype = do -- epoch and ciphersuite, regardless of the API version n <- awaitMatch isConvCreateNotif ws n %. "payload.0.data.epoch" `shouldMatchInt` 0 - n %. "payload.0.data.cipher_suite" `shouldMatchInt` 1 + n %. "payload.0.data.cipher_suite" `shouldMatchInt` 2 pure qcnv resp <- createAddCommit alice1 qcnv [bob] >>= sendAndConsumeCommitBundle @@ -516,7 +516,7 @@ testSelfConversation v = withVersion5 v $ do convId <- objConvId conv conv %. "epoch" `shouldMatchInt` 0 case v of - Version5 -> conv %. "cipher_suite" `shouldMatchInt` 1 + Version5 -> conv %. "cipher_suite" `shouldMatchInt` 2 NoVersion5 -> assertFieldMissing conv "cipher_suite" void $ createAddCommit creator convId [alice] >>= sendAndConsumeCommitBundle diff --git a/integration/test/Test/MLS/KeyPackage.hs b/integration/test/Test/MLS/KeyPackage.hs index 2c49f0206d7..830b1889720 100644 --- a/integration/test/Test/MLS/KeyPackage.hs +++ b/integration/test/Test/MLS/KeyPackage.hs @@ -11,9 +11,10 @@ import Testlib.Prelude testDeleteKeyPackages :: App () testDeleteKeyPackages = do + let suite = Ciphersuite "0x0001" alice <- randomUser OwnDomain def - alice1 <- createMLSClient def def alice - kps <- replicateM 3 (uploadNewKeyPackage def alice1) + alice1 <- createMLSClient suite def alice + kps <- replicateM 3 (uploadNewKeyPackage suite alice1) -- add an extra non-existing key package to the delete request let kps' = "4B701F521EBE82CEC4AD5CB67FDD8E1C43FC4868DE32D03933CE4993160B75E8" : kps @@ -27,26 +28,30 @@ testDeleteKeyPackages = do testKeyPackageMultipleCiphersuites :: App () testKeyPackageMultipleCiphersuites = do + let suite = Ciphersuite "0x0001" alice <- randomUser OwnDomain def - [alice1, alice2] <- replicateM 2 (createMLSClient def def alice) + [alice1, alice2] <- replicateM 2 (createMLSClient suite def alice) - kp <- uploadNewKeyPackage def alice2 + kp <- uploadNewKeyPackage suite alice2 - let suite = Ciphersuite "0xf031" - void $ uploadNewKeyPackage suite alice2 + -- Using 0xf031 as the alternative for 0x0001 is possible without creating a + -- new signature key for this client, since both cipher suites share the same + -- signature scheme. + let altSuite = Ciphersuite "0xf031" + void $ uploadNewKeyPackage altSuite alice2 - -- count key packages with default ciphersuite - bindResponse (countKeyPackages def alice2) $ \resp -> do + -- count key packages with the client's default ciphersuite + bindResponse (countKeyPackages suite alice2) $ \resp -> do resp.status `shouldMatchInt` 200 resp.json %. "count" `shouldMatchInt` 1 - -- claim key packages with default ciphersuite - bindResponse (claimKeyPackages def alice1 alice) $ \resp -> do + -- claim key packages with the client's default ciphersuite + bindResponse (claimKeyPackages suite alice1 alice) $ \resp -> do resp.status `shouldMatchInt` 200 resp.json %. "key_packages.0.key_package_ref" `shouldMatch` kp -- count key package with the other ciphersuite - bindResponse (countKeyPackages suite alice2) $ \resp -> do + bindResponse (countKeyPackages altSuite alice2) $ \resp -> do resp.status `shouldMatchInt` 200 resp.json %. "count" `shouldMatchInt` 1 @@ -207,9 +212,10 @@ testUnsupportedCiphersuite = do testReplaceKeyPackages :: (HasCallStack) => App () testReplaceKeyPackages = do + let suite = Ciphersuite "0x0001" + altSuite = Ciphersuite "0xf031" alice <- randomUser OwnDomain def - [alice1, alice2] <- replicateM 2 $ createMLSClient def def alice - let suite = Ciphersuite "0xf031" + [alice1, alice2] <- replicateM 2 $ createMLSClient suite def alice let checkCount cs n = bindResponse (countKeyPackages cs alice1) $ \resp -> do @@ -218,31 +224,31 @@ testReplaceKeyPackages = do -- setup: upload a batch of key packages for each ciphersuite void - $ replicateM 4 (fmap fst (generateKeyPackage alice1 def)) + $ replicateM 4 (fmap fst (generateKeyPackage alice1 suite)) >>= uploadKeyPackages alice1 >>= getBody 201 void - $ replicateM 5 (fmap fst (generateKeyPackage alice1 suite)) + $ replicateM 5 (fmap fst (generateKeyPackage alice1 altSuite)) >>= uploadKeyPackages alice1 >>= getBody 201 - checkCount def 4 - checkCount suite 5 + checkCount suite 4 + checkCount altSuite 5 do -- generate a new batch of key packages - (kps, refs) <- unzip <$> replicateM 3 (generateKeyPackage alice1 suite) + (kps, refs) <- unzip <$> replicateM 3 (generateKeyPackage alice1 altSuite) -- replace old key packages with new - void $ replaceKeyPackages alice1 (Just [suite]) kps >>= getBody 201 + void $ replaceKeyPackages alice1 (Just [altSuite]) kps >>= getBody 201 - checkCount def 4 - checkCount suite 3 + checkCount suite 4 + checkCount altSuite 3 -- claim all key packages one by one claimed <- replicateM 3 - $ bindResponse (claimKeyPackages suite alice2 alice) + $ bindResponse (claimKeyPackages altSuite alice2 alice) $ \resp -> do resp.status `shouldMatchInt` 200 ks <- resp.json %. "key_packages" & asList @@ -251,31 +257,31 @@ testReplaceKeyPackages = do refs `shouldMatchSet` claimed - checkCount def 4 - checkCount suite 0 + checkCount suite 4 + checkCount altSuite 0 do -- replenish key packages for the second ciphersuite void - $ replicateM 5 (fmap fst (generateKeyPackage alice1 suite)) + $ replicateM 5 (fmap fst (generateKeyPackage alice1 altSuite)) >>= uploadKeyPackages alice1 >>= getBody 201 - checkCount def 4 - checkCount suite 5 + checkCount suite 4 + checkCount altSuite 5 -- replace all key packages with fresh ones - kps1 <- replicateM 2 (fmap fst (generateKeyPackage alice1 def)) - kps2 <- replicateM 2 (fmap fst (generateKeyPackage alice1 suite)) + kps1 <- replicateM 2 (fmap fst (generateKeyPackage alice1 suite)) + kps2 <- replicateM 2 (fmap fst (generateKeyPackage alice1 altSuite)) - void $ replaceKeyPackages alice1 (Just [def, suite]) (kps1 <> kps2) >>= getBody 201 + void $ replaceKeyPackages alice1 (Just [suite, altSuite]) (kps1 <> kps2) >>= getBody 201 - checkCount def 2 checkCount suite 2 + checkCount altSuite 2 do - defKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 def)) suiteKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 suite)) + altSuiteKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 altSuite)) void $ replaceKeyPackages alice1 (Just []) [] @@ -283,12 +289,12 @@ testReplaceKeyPackages = do resp.status `shouldMatchInt` 201 void - $ replaceKeyPackages alice1 Nothing defKeyPackages + $ replaceKeyPackages alice1 Nothing suiteKeyPackages `bindResponse` \resp -> do resp.status `shouldMatchInt` 201 - checkCount def 3 - checkCount suite 2 + checkCount suite 3 + checkCount altSuite 2 let testErrorCases :: (HasCallStack) => Maybe [Ciphersuite] -> [ByteString] -> App () testErrorCases ciphersuites keyPackages = do @@ -297,19 +303,19 @@ testReplaceKeyPackages = do `bindResponse` \resp -> do resp.status `shouldMatchInt` 400 resp.json %. "label" `shouldMatch` "mls-protocol-error" - checkCount def 3 - checkCount suite 2 + checkCount suite 3 + checkCount altSuite 2 - testErrorCases (Just []) defKeyPackages testErrorCases (Just []) suiteKeyPackages + testErrorCases (Just []) altSuiteKeyPackages testErrorCases Nothing [] - testErrorCases Nothing suiteKeyPackages - testErrorCases Nothing (suiteKeyPackages <> defKeyPackages) + testErrorCases Nothing altSuiteKeyPackages + testErrorCases Nothing (altSuiteKeyPackages <> suiteKeyPackages) - testErrorCases (Just [suite]) defKeyPackages - testErrorCases (Just [suite]) (suiteKeyPackages <> defKeyPackages) - testErrorCases (Just [suite]) [] + testErrorCases (Just [altSuite]) suiteKeyPackages + testErrorCases (Just [altSuite]) (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases (Just [altSuite]) [] - testErrorCases (Just [def]) suiteKeyPackages - testErrorCases (Just [def]) (suiteKeyPackages <> defKeyPackages) - testErrorCases (Just [def]) [] + testErrorCases (Just [suite]) altSuiteKeyPackages + testErrorCases (Just [suite]) (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases (Just [suite]) [] diff --git a/integration/test/Test/MLS/One2One.hs b/integration/test/Test/MLS/One2One.hs index 660368b7660..55678db4c60 100644 --- a/integration/test/Test/MLS/One2One.hs +++ b/integration/test/Test/MLS/One2One.hs @@ -39,7 +39,7 @@ testGetMLSOne2OneLocalV5 = withVersion5 Version5 $ do [alice, bob] <- createAndConnectUsers [OwnDomain, OwnDomain] let assertConvData conv = do conv %. "epoch" `shouldMatchInt` 0 - conv %. "cipher_suite" `shouldMatchInt` 1 + conv %. "cipher_suite" `shouldMatchInt` 2 convId <- getMLSOne2OneConversationLegacy alice bob `bindResponse` \resp -> do diff --git a/integration/test/Testlib/Types.hs b/integration/test/Testlib/Types.hs index bdb732c6831..aa3a1639aa6 100644 --- a/integration/test/Testlib/Types.hs +++ b/integration/test/Testlib/Types.hs @@ -261,7 +261,7 @@ newtype Ciphersuite = Ciphersuite {code :: String} deriving (Eq, Ord, Show, Generic) instance Default Ciphersuite where - def = Ciphersuite "0x0001" + def = Ciphersuite "0x0002" data ClientGroupState = ClientGroupState { groups :: Map ConvId ByteString, diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json index dbfa3343671..032447a6c4e 100644 --- a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json @@ -28,7 +28,7 @@ "type": 2 }, "protocol": { - "cipher_suite": 1, + "cipher_suite": 2, "epoch": 0, "epoch_timestamp": null, "group_id": "Z3JvdXA=", diff --git a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs index ef4be957f28..67cb09d2e4b 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs @@ -109,7 +109,7 @@ optionalActiveMLSConversationDataSchema (Just v) schema <*> fmap (.epochTimestamp) .= field "epoch_timestamp" (named "EpochTimestamp" . nullable . unnamed $ utcTimeSchema) - <*> maybe MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 (.ciphersuite) + <*> maybe MLS_128_DHKEMP256_AES128GCM_SHA256_P256 (.ciphersuite) .= fieldWithDocModifier "cipher_suite" (description ?~ "The cipher suite of the corresponding MLS group") diff --git a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs index 5f93d01f8c3..4723621b887 100644 --- a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs +++ b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs @@ -21,6 +21,7 @@ module Wire.API.MLS.CipherSuite ( -- * MLS ciphersuites CipherSuite (..), defCipherSuite, + defCipherSuiteV7, CipherSuiteTag (..), cipherSuiteTag, tagCipherSuite, @@ -122,7 +123,13 @@ data CipherSuiteTag deriving (Arbitrary) via (GenericUniform CipherSuiteTag) defCipherSuite :: CipherSuiteTag -defCipherSuite = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 +defCipherSuite = MLS_128_DHKEMP256_AES128GCM_SHA256_P256 + +-- | default cipher suite as of wire-server 5.8 +-- This cipher suite is used for backward-compatibility in endpoints which did +-- not support choosing the cipher suite in a query parameter from the start. +defCipherSuiteV7 :: CipherSuiteTag +defCipherSuiteV7 = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 instance S.ToSchema CipherSuiteTag where declareNamedSchema _ = diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index 533bbd8837f..493aaa384a4 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -113,7 +113,7 @@ import Test.QuickCheck (getPrintableString) import Test.QuickCheck.Arbitrary (arbitrary) import Test.QuickCheck.Gen (suchThat) import Wire.API.Conversation.Protocol -import Wire.API.MLS.CipherSuite (CipherSuiteTag (MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519)) +import Wire.API.MLS.CipherSuite import Wire.API.Routes.Named import Wire.Arbitrary (Arbitrary, GenericUniform (..)) @@ -925,8 +925,8 @@ instance Default MLSConfig where MLSConfig [] ProtocolProteusTag - [MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519] - MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 + [MLS_128_DHKEMP256_AES128GCM_SHA256_P256] + MLS_128_DHKEMP256_AES128GCM_SHA256_P256 [ProtocolProteusTag, ProtocolMLSTag] instance ToSchema MLSConfig where diff --git a/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json b/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json index cda477d4510..8eab7834e96 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json +++ b/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json @@ -19,7 +19,7 @@ "guest", "service" ], - "cipher_suite": 1, + "cipher_suite": 2, "creator": "00000000-0000-0000-0000-000200000001", "epoch": 0, "epoch_timestamp": null, diff --git a/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json b/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json index e7351a003ce..6ea55d98066 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json +++ b/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json @@ -18,7 +18,7 @@ "guest", "service" ], - "cipher_suite": 1, + "cipher_suite": 2, "creator": "00000000-0000-0000-0000-000200000001", "epoch": 0, "epoch_timestamp": null, diff --git a/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json b/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json index a918c3161ba..edde0bc7c60 100644 --- a/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json +++ b/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json @@ -1,5 +1,5 @@ { - "cipher_suite": 1, + "cipher_suite": 2, "epoch": 0, "epoch_timestamp": null, "group_id": "dGVzdF9ncm91cF8y", diff --git a/services/brig/src/Brig/API/MLS/CipherSuite.hs b/services/brig/src/Brig/API/MLS/CipherSuite.hs index c47bd0fedaa..226cf272705 100644 --- a/services/brig/src/Brig/API/MLS/CipherSuite.hs +++ b/services/brig/src/Brig/API/MLS/CipherSuite.hs @@ -33,14 +33,14 @@ getOneCipherSuite s = (cipherSuiteTag s) getCipherSuite :: Maybe CipherSuite -> Handler r CipherSuiteTag -getCipherSuite = maybe (pure defCipherSuite) getOneCipherSuite +getCipherSuite = maybe (pure defCipherSuiteV7) getOneCipherSuite validateCipherSuites :: Maybe [CipherSuite] -> KeyPackageUpload -> Handler r (Set CipherSuiteTag) validateCipherSuites suites upload = do - suitesQuery <- Set.fromList <$> maybe (pure [defCipherSuite]) (traverse getOneCipherSuite) suites + suitesQuery <- Set.fromList <$> maybe (pure [defCipherSuiteV7]) (traverse getOneCipherSuite) suites when (any isNothing suitesKPM) . void $ mlsProtocolError "uploaded key packages contains unsupported cipher suite" unless (suitesQuery == suitesKP) . void $ mlsProtocolError "uploaded key packages for unannounced cipher suites" pure suitesQuery From 0a58c7b7e82150f7c308ae518c366a821902c987 Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Tue, 17 Dec 2024 11:16:09 +0000 Subject: [PATCH 02/12] fix testMLSFederationV1ConvOnOldBackend and testMLSFederationV1ConvOnNewBackend --- integration/test/Test/MLS/One2One.hs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/integration/test/Test/MLS/One2One.hs b/integration/test/Test/MLS/One2One.hs index 55678db4c60..26bc3460df3 100644 --- a/integration/test/Test/MLS/One2One.hs +++ b/integration/test/Test/MLS/One2One.hs @@ -405,6 +405,7 @@ testMLSGhostOne2OneConv = do -- See `deploy/dockerephemeral/run.sh` and comment on `StaticFedDomain` in `Testlib/VersionedFed.hs` for more details. testMLSFederationV1ConvOnOldBackend :: App () testMLSFederationV1ConvOnOldBackend = do + let cs = Ciphersuite "0x0001" alice <- randomUser OwnDomain def let createBob = do bobCandidate <- randomUser (StaticFedDomain 1) def @@ -415,8 +416,8 @@ testMLSFederationV1ConvOnOldBackend = do else createBob bob <- createBob - [alice1, bob1] <- traverse (createMLSClient def def) [alice, bob] - void $ uploadNewKeyPackage def alice1 + [alice1, bob1] <- traverse (createMLSClient cs def) [alice, bob] + void $ uploadNewKeyPackage cs alice1 -- Alice cannot start this conversation because it would exist on Bob's -- backend and Alice cannot get the MLS public keys of that backend. @@ -427,7 +428,7 @@ testMLSFederationV1ConvOnOldBackend = do conv <- getMLSOne2OneConversationLegacy bob alice >>= getJSON 200 convId <- objConvId conv keys <- getMLSPublicKeys bob >>= getJSON 200 - resetOne2OneGroupGeneric def bob1 conv keys + resetOne2OneGroupGeneric cs bob1 conv keys withWebSocket alice1 $ \wsAlice -> do commit <- createAddCommit bob1 convId [alice] @@ -448,9 +449,9 @@ testMLSFederationV1ConvOnOldBackend = do mlsMsg <- asByteString (nPayload n %. "data") -- Checks that the remove proposal is consumable by bob - void $ mlsCliConsume convId def bob1 mlsMsg + void $ mlsCliConsume convId cs bob1 mlsMsg - parsedMsg <- showMessage def bob1 mlsMsg + parsedMsg <- showMessage cs bob1 mlsMsg let leafIndexAlice = 1 parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexAlice parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 @@ -460,6 +461,7 @@ testMLSFederationV1ConvOnOldBackend = do -- See `deploy/dockerephemeral/run.sh` and comment on `StaticFedDomain` in `Testlib/VersionedFed.hs` for more details. testMLSFederationV1ConvOnNewBackend :: App () testMLSFederationV1ConvOnNewBackend = do + let cs = Ciphersuite "0x0001" alice <- randomUser OwnDomain def let createBob = do bobCandidate <- randomUser (StaticFedDomain 1) def @@ -470,8 +472,8 @@ testMLSFederationV1ConvOnNewBackend = do else createBob bob <- createBob - [alice1, bob1] <- traverse (createMLSClient def def) [alice, bob] - void $ uploadNewKeyPackage def bob1 + [alice1, bob1] <- traverse (createMLSClient cs def) [alice, bob] + void $ uploadNewKeyPackage cs bob1 -- Bob cannot start this conversation because it would exist on Alice's -- backend and Bob cannot get the MLS public keys of that backend. @@ -482,7 +484,7 @@ testMLSFederationV1ConvOnNewBackend = do one2OneConv <- getMLSOne2OneConversation alice bob >>= getJSON 200 one2OneConvId <- objConvId $ one2OneConv %. "conversation" conv <- one2OneConv %. "conversation" - resetOne2OneGroup def alice1 one2OneConv + resetOne2OneGroup cs alice1 one2OneConv withWebSocket bob1 $ \wsBob -> do commit <- createAddCommit alice1 one2OneConvId [bob] @@ -503,9 +505,9 @@ testMLSFederationV1ConvOnNewBackend = do mlsMsg <- asByteString (nPayload n %. "data") -- Checks that the remove proposal is consumable by bob - void $ mlsCliConsume one2OneConvId def alice1 mlsMsg + void $ mlsCliConsume one2OneConvId cs alice1 mlsMsg - parsedMsg <- showMessage def alice1 mlsMsg + parsedMsg <- showMessage cs alice1 mlsMsg let leafIndexBob = 1 parsedMsg %. "message.content.body.Proposal.Remove.removed" `shouldMatchInt` leafIndexBob parsedMsg %. "message.content.sender.External" `shouldMatchInt` 0 From 403a353ee4e36059307ca4eede389da3cc0be08e Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Tue, 17 Dec 2024 15:29:46 +0000 Subject: [PATCH 03/12] restore MLS.KeyPackage.testDeleteKeyPackages --- integration/test/API/Brig.hs | 9 ++++++--- integration/test/Test/MLS/KeyPackage.hs | 7 +++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/integration/test/API/Brig.hs b/integration/test/API/Brig.hs index b788d3c6f74..1825d27792b 100644 --- a/integration/test/API/Brig.hs +++ b/integration/test/API/Brig.hs @@ -358,10 +358,13 @@ countKeyPackages suite cid = do req & addQueryParams [("ciphersuite", suite.code)] -deleteKeyPackages :: ClientIdentity -> [String] -> App Response -deleteKeyPackages cid kps = do +deleteKeyPackages :: Ciphersuite -> ClientIdentity -> [String] -> App Response +deleteKeyPackages suite cid kps = do req <- baseRequest cid Brig Versioned ("/mls/key-packages/self/" <> cid.client) - submit "DELETE" $ req & addJSONObject ["key_packages" .= kps] + submit "DELETE" $ + req + & addQueryParams [("ciphersuite", suite.code)] + & addJSONObject ["key_packages" .= kps] replaceKeyPackages :: ClientIdentity -> Maybe [Ciphersuite] -> [ByteString] -> App Response replaceKeyPackages cid mSuites kps = do diff --git a/integration/test/Test/MLS/KeyPackage.hs b/integration/test/Test/MLS/KeyPackage.hs index 830b1889720..074e41f61f4 100644 --- a/integration/test/Test/MLS/KeyPackage.hs +++ b/integration/test/Test/MLS/KeyPackage.hs @@ -11,15 +11,14 @@ import Testlib.Prelude testDeleteKeyPackages :: App () testDeleteKeyPackages = do - let suite = Ciphersuite "0x0001" alice <- randomUser OwnDomain def - alice1 <- createMLSClient suite def alice - kps <- replicateM 3 (uploadNewKeyPackage suite alice1) + alice1 <- createMLSClient def def alice + kps <- replicateM 3 (uploadNewKeyPackage def alice1) -- add an extra non-existing key package to the delete request let kps' = "4B701F521EBE82CEC4AD5CB67FDD8E1C43FC4868DE32D03933CE4993160B75E8" : kps - bindResponse (deleteKeyPackages alice1 kps') $ \resp -> do + bindResponse (deleteKeyPackages def alice1 kps') $ \resp -> do resp.status `shouldMatchInt` 201 bindResponse (countKeyPackages def alice1) $ \resp -> do From 232ac8109a366ae78819fb3609ff27dda94730d5 Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Tue, 17 Dec 2024 16:25:22 +0000 Subject: [PATCH 04/12] make key package tests work with different cipher suites --- integration/test/MLS/Util.hs | 15 ++++++++-- integration/test/Test/MLS/KeyPackage.hs | 39 +++++++++++++++---------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/integration/test/MLS/Util.hs b/integration/test/MLS/Util.hs index 68e9fcb3a12..6814757281a 100644 --- a/integration/test/MLS/Util.hs +++ b/integration/test/MLS/Util.hs @@ -156,18 +156,27 @@ instance Default InitMLSClient where -- | Create new mls client and register with backend. createMLSClient :: (MakesValue u, HasCallStack) => Ciphersuite -> InitMLSClient -> u -> App ClientIdentity -createMLSClient ciphersuite opts u = do +createMLSClient ciphersuite = createMLSClientWithCiphersuites [ciphersuite] + +-- | Create new mls client and register with backend. +createMLSClientWithCiphersuites :: (MakesValue u, HasCallStack) => [Ciphersuite] -> InitMLSClient -> u -> App ClientIdentity +createMLSClientWithCiphersuites ciphersuites opts u = do cid <- createWireClient u opts.clientArgs setClientGroupState cid def {credType = opts.credType} -- set public key - pkey <- mlscli Nothing ciphersuite cid ["public-key"] Nothing + suitePKeys <- for ciphersuites $ \ciphersuite -> (ciphersuite,) <$> mlscli Nothing ciphersuite cid ["public-key"] Nothing bindResponse ( updateClient cid def { mlsPublicKeys = - Just (object [csSignatureScheme ciphersuite .= T.decodeUtf8 (Base64.encode pkey)]) + Just + ( object + [ csSignatureScheme ciphersuite .= T.decodeUtf8 (Base64.encode pkey) + | (ciphersuite, pkey) <- suitePKeys + ] + ) } ) $ \resp -> resp.status `shouldMatchInt` 200 diff --git a/integration/test/Test/MLS/KeyPackage.hs b/integration/test/Test/MLS/KeyPackage.hs index 074e41f61f4..40a38642b07 100644 --- a/integration/test/Test/MLS/KeyPackage.hs +++ b/integration/test/Test/MLS/KeyPackage.hs @@ -27,16 +27,13 @@ testDeleteKeyPackages = do testKeyPackageMultipleCiphersuites :: App () testKeyPackageMultipleCiphersuites = do - let suite = Ciphersuite "0x0001" + let suite = def + altSuite = Ciphersuite "0x0005" alice <- randomUser OwnDomain def - [alice1, alice2] <- replicateM 2 (createMLSClient suite def alice) + [alice1, alice2] <- replicateM 2 (createMLSClientWithCiphersuites [suite, altSuite] def alice) kp <- uploadNewKeyPackage suite alice2 - -- Using 0xf031 as the alternative for 0x0001 is possible without creating a - -- new signature key for this client, since both cipher suites share the same - -- signature scheme. - let altSuite = Ciphersuite "0xf031" void $ uploadNewKeyPackage altSuite alice2 -- count key packages with the client's default ciphersuite @@ -211,12 +208,14 @@ testUnsupportedCiphersuite = do testReplaceKeyPackages :: (HasCallStack) => App () testReplaceKeyPackages = do - let suite = Ciphersuite "0x0001" - altSuite = Ciphersuite "0xf031" + let suite = def + altSuite = Ciphersuite "0x0005" + oldSuite = Ciphersuite "0x0001" alice <- randomUser OwnDomain def - [alice1, alice2] <- replicateM 2 $ createMLSClient suite def alice + [alice1, alice2] <- replicateM 2 $ createMLSClientWithCiphersuites [suite, altSuite, oldSuite] def alice - let checkCount cs n = + let checkCount :: (HasCallStack) => Ciphersuite -> Int -> App () + checkCount cs n = bindResponse (countKeyPackages cs alice1) $ \resp -> do resp.status `shouldMatchInt` 200 resp.json %. "count" `shouldMatchInt` n @@ -230,9 +229,14 @@ testReplaceKeyPackages = do $ replicateM 5 (fmap fst (generateKeyPackage alice1 altSuite)) >>= uploadKeyPackages alice1 >>= getBody 201 + void + $ replicateM 6 (fmap fst (generateKeyPackage alice1 oldSuite)) + >>= uploadKeyPackages alice1 + >>= getBody 201 checkCount suite 4 checkCount altSuite 5 + checkCount oldSuite 6 do -- generate a new batch of key packages @@ -270,17 +274,18 @@ testReplaceKeyPackages = do checkCount altSuite 5 -- replace all key packages with fresh ones - kps1 <- replicateM 2 (fmap fst (generateKeyPackage alice1 suite)) + kps1 <- replicateM 3 (fmap fst (generateKeyPackage alice1 suite)) kps2 <- replicateM 2 (fmap fst (generateKeyPackage alice1 altSuite)) void $ replaceKeyPackages alice1 (Just [suite, altSuite]) (kps1 <> kps2) >>= getBody 201 - checkCount suite 2 + checkCount suite 3 checkCount altSuite 2 do suiteKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 suite)) altSuiteKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 altSuite)) + oldSuiteKeyPackages <- replicateM 4 (fmap fst (generateKeyPackage alice1 oldSuite)) void $ replaceKeyPackages alice1 (Just []) [] @@ -288,12 +293,13 @@ testReplaceKeyPackages = do resp.status `shouldMatchInt` 201 void - $ replaceKeyPackages alice1 Nothing suiteKeyPackages + $ replaceKeyPackages alice1 Nothing oldSuiteKeyPackages `bindResponse` \resp -> do resp.status `shouldMatchInt` 201 checkCount suite 3 checkCount altSuite 2 + checkCount oldSuite 4 let testErrorCases :: (HasCallStack) => Maybe [Ciphersuite] -> [ByteString] -> App () testErrorCases ciphersuites keyPackages = do @@ -304,17 +310,18 @@ testReplaceKeyPackages = do resp.json %. "label" `shouldMatch` "mls-protocol-error" checkCount suite 3 checkCount altSuite 2 + checkCount oldSuite 4 testErrorCases (Just []) suiteKeyPackages testErrorCases (Just []) altSuiteKeyPackages testErrorCases Nothing [] testErrorCases Nothing altSuiteKeyPackages - testErrorCases Nothing (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases Nothing (oldSuiteKeyPackages <> altSuiteKeyPackages <> suiteKeyPackages) testErrorCases (Just [altSuite]) suiteKeyPackages - testErrorCases (Just [altSuite]) (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases (Just [altSuite]) (oldSuiteKeyPackages <> altSuiteKeyPackages <> suiteKeyPackages) testErrorCases (Just [altSuite]) [] testErrorCases (Just [suite]) altSuiteKeyPackages - testErrorCases (Just [suite]) (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases (Just [suite]) (oldSuiteKeyPackages <> altSuiteKeyPackages <> suiteKeyPackages) testErrorCases (Just [suite]) [] From efa228ae8d08fc3438da87c84d03a00d5239331e Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Wed, 18 Dec 2024 09:20:36 +0000 Subject: [PATCH 05/12] add release notes with warning about breaking clients --- changelog.d/0-release-notes/WPB-15004 | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 changelog.d/0-release-notes/WPB-15004 diff --git a/changelog.d/0-release-notes/WPB-15004 b/changelog.d/0-release-notes/WPB-15004 new file mode 100644 index 00000000000..6d444b2ea01 --- /dev/null +++ b/changelog.d/0-release-notes/WPB-15004 @@ -0,0 +1,27 @@ +We changed the default MLS cipher suite from + +- MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 + +to + +- MLS_128_DHKEMP256_AES128GCM_SHA256_P256 + +and the allowed MLS cipher suites from only + +- MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 + +to _only_ + +- MLS_128_DHKEMP256_AES128GCM_SHA256_P256. + +ATTENTION: This breaks your MLS clients if they used the previous defaults +before. This is even true if you allow several cipher suites, since current MLS +clients only support _one_ cipher suite at a time. + +[Adjust the defaults in the server +configuration](https://github.com/wireapp/wire-server/blob/develop/docs/src/developer/reference/config-options.md#mls) +to switch the values of `defaultCipherSuite` and `allowedCipherSuites` back to +the previous defaults, `1` and `[1]`, respectively. Once MLS clients support +several cipher suites, you could even use `[1,2]` or a list of other cipher +suites in `allowedCipherSuites`. Make sure that this list contains the currently +used cipher suite! From c399b1dfc5095502cd889247f019077f14f8ca0a Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Wed, 18 Dec 2024 17:06:43 +0000 Subject: [PATCH 06/12] switch to 0x0007 instead of 0x0005 - 0x0005 is unsupported by Rust lib --- integration/test/Test/MLS/KeyPackage.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/test/Test/MLS/KeyPackage.hs b/integration/test/Test/MLS/KeyPackage.hs index 40a38642b07..df51afe2237 100644 --- a/integration/test/Test/MLS/KeyPackage.hs +++ b/integration/test/Test/MLS/KeyPackage.hs @@ -28,7 +28,7 @@ testDeleteKeyPackages = do testKeyPackageMultipleCiphersuites :: App () testKeyPackageMultipleCiphersuites = do let suite = def - altSuite = Ciphersuite "0x0005" + altSuite = Ciphersuite "0x0007" alice <- randomUser OwnDomain def [alice1, alice2] <- replicateM 2 (createMLSClientWithCiphersuites [suite, altSuite] def alice) @@ -209,7 +209,7 @@ testUnsupportedCiphersuite = do testReplaceKeyPackages :: (HasCallStack) => App () testReplaceKeyPackages = do let suite = def - altSuite = Ciphersuite "0x0005" + altSuite = Ciphersuite "0x0007" oldSuite = Ciphersuite "0x0001" alice <- randomUser OwnDomain def [alice1, alice2] <- replicateM 2 $ createMLSClientWithCiphersuites [suite, altSuite, oldSuite] def alice From 8f7e0e5267d1a939d897069057ca0d9773564a2b Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Wed, 18 Dec 2024 17:08:00 +0000 Subject: [PATCH 07/12] preserve ciphersuite 0x0001 in events and older API versions --- integration/test/Test/MLS.hs | 4 ++-- integration/test/Test/MLS/One2One.hs | 2 +- libs/wire-api/src/Wire/API/Conversation/Protocol.hs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/test/Test/MLS.hs b/integration/test/Test/MLS.hs index 7c1ab6e88ab..856f480e983 100644 --- a/integration/test/Test/MLS.hs +++ b/integration/test/Test/MLS.hs @@ -390,7 +390,7 @@ testAddUserSimple suite ctype = do -- epoch and ciphersuite, regardless of the API version n <- awaitMatch isConvCreateNotif ws n %. "payload.0.data.epoch" `shouldMatchInt` 0 - n %. "payload.0.data.cipher_suite" `shouldMatchInt` 2 + n %. "payload.0.data.cipher_suite" `shouldMatchInt` 1 pure qcnv resp <- createAddCommit alice1 qcnv [bob] >>= sendAndConsumeCommitBundle @@ -516,7 +516,7 @@ testSelfConversation v = withVersion5 v $ do convId <- objConvId conv conv %. "epoch" `shouldMatchInt` 0 case v of - Version5 -> conv %. "cipher_suite" `shouldMatchInt` 2 + Version5 -> conv %. "cipher_suite" `shouldMatchInt` 1 NoVersion5 -> assertFieldMissing conv "cipher_suite" void $ createAddCommit creator convId [alice] >>= sendAndConsumeCommitBundle diff --git a/integration/test/Test/MLS/One2One.hs b/integration/test/Test/MLS/One2One.hs index 26bc3460df3..bac143fcef9 100644 --- a/integration/test/Test/MLS/One2One.hs +++ b/integration/test/Test/MLS/One2One.hs @@ -39,7 +39,7 @@ testGetMLSOne2OneLocalV5 = withVersion5 Version5 $ do [alice, bob] <- createAndConnectUsers [OwnDomain, OwnDomain] let assertConvData conv = do conv %. "epoch" `shouldMatchInt` 0 - conv %. "cipher_suite" `shouldMatchInt` 2 + conv %. "cipher_suite" `shouldMatchInt` 1 convId <- getMLSOne2OneConversationLegacy alice bob `bindResponse` \resp -> do diff --git a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs index 67cb09d2e4b..ef4be957f28 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Protocol.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Protocol.hs @@ -109,7 +109,7 @@ optionalActiveMLSConversationDataSchema (Just v) schema <*> fmap (.epochTimestamp) .= field "epoch_timestamp" (named "EpochTimestamp" . nullable . unnamed $ utcTimeSchema) - <*> maybe MLS_128_DHKEMP256_AES128GCM_SHA256_P256 (.ciphersuite) + <*> maybe MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 (.ciphersuite) .= fieldWithDocModifier "cipher_suite" (description ?~ "The cipher suite of the corresponding MLS group") From c0ac300b80c126a850e6dbf08a2d2bed39c6a244 Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Thu, 19 Dec 2024 08:27:57 +0000 Subject: [PATCH 08/12] fixup! preserve ciphersuite 0x0001 in events and older API versions --- .../golden/testObject_GetOne2OneConversationResponseOk.json | 2 +- .../wire-api/test/golden/testObject_Conversation_v2_user_4.json | 2 +- .../wire-api/test/golden/testObject_Conversation_v5_user_4.json | 2 +- .../test/golden/testObject_PublicSubConversation_v5_2.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json index 032447a6c4e..dbfa3343671 100644 --- a/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json +++ b/libs/wire-api-federation/test/golden/testObject_GetOne2OneConversationResponseOk.json @@ -28,7 +28,7 @@ "type": 2 }, "protocol": { - "cipher_suite": 2, + "cipher_suite": 1, "epoch": 0, "epoch_timestamp": null, "group_id": "Z3JvdXA=", diff --git a/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json b/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json index 8eab7834e96..cda477d4510 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json +++ b/libs/wire-api/test/golden/testObject_Conversation_v2_user_4.json @@ -19,7 +19,7 @@ "guest", "service" ], - "cipher_suite": 2, + "cipher_suite": 1, "creator": "00000000-0000-0000-0000-000200000001", "epoch": 0, "epoch_timestamp": null, diff --git a/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json b/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json index 6ea55d98066..e7351a003ce 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json +++ b/libs/wire-api/test/golden/testObject_Conversation_v5_user_4.json @@ -18,7 +18,7 @@ "guest", "service" ], - "cipher_suite": 2, + "cipher_suite": 1, "creator": "00000000-0000-0000-0000-000200000001", "epoch": 0, "epoch_timestamp": null, diff --git a/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json b/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json index edde0bc7c60..a918c3161ba 100644 --- a/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json +++ b/libs/wire-api/test/golden/testObject_PublicSubConversation_v5_2.json @@ -1,5 +1,5 @@ { - "cipher_suite": 2, + "cipher_suite": 1, "epoch": 0, "epoch_timestamp": null, "group_id": "dGVzdF9ncm91cF8y", From ef684d9e9b1ba2981ce50562e42add00bbd09d90 Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Thu, 19 Dec 2024 08:40:30 +0000 Subject: [PATCH 09/12] rename defCipherSuiteV7 to legacyCipherSuite --- libs/wire-api/src/Wire/API/MLS/CipherSuite.hs | 10 +++------- services/brig/src/Brig/API/MLS/CipherSuite.hs | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs index 4723621b887..a9703d6decb 100644 --- a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs +++ b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs @@ -20,8 +20,7 @@ module Wire.API.MLS.CipherSuite ( -- * MLS ciphersuites CipherSuite (..), - defCipherSuite, - defCipherSuiteV7, + legacyCipherSuite, CipherSuiteTag (..), cipherSuiteTag, tagCipherSuite, @@ -122,14 +121,11 @@ data CipherSuiteTag deriving stock (Bounded, Enum, Eq, Show, Generic, Ord) deriving (Arbitrary) via (GenericUniform CipherSuiteTag) -defCipherSuite :: CipherSuiteTag -defCipherSuite = MLS_128_DHKEMP256_AES128GCM_SHA256_P256 - -- | default cipher suite as of wire-server 5.8 -- This cipher suite is used for backward-compatibility in endpoints which did -- not support choosing the cipher suite in a query parameter from the start. -defCipherSuiteV7 :: CipherSuiteTag -defCipherSuiteV7 = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 +legacyCipherSuite :: CipherSuiteTag +legacyCipherSuite = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 instance S.ToSchema CipherSuiteTag where declareNamedSchema _ = diff --git a/services/brig/src/Brig/API/MLS/CipherSuite.hs b/services/brig/src/Brig/API/MLS/CipherSuite.hs index 226cf272705..21093b8c12f 100644 --- a/services/brig/src/Brig/API/MLS/CipherSuite.hs +++ b/services/brig/src/Brig/API/MLS/CipherSuite.hs @@ -33,14 +33,14 @@ getOneCipherSuite s = (cipherSuiteTag s) getCipherSuite :: Maybe CipherSuite -> Handler r CipherSuiteTag -getCipherSuite = maybe (pure defCipherSuiteV7) getOneCipherSuite +getCipherSuite = maybe (pure legacyCipherSuite) getOneCipherSuite validateCipherSuites :: Maybe [CipherSuite] -> KeyPackageUpload -> Handler r (Set CipherSuiteTag) validateCipherSuites suites upload = do - suitesQuery <- Set.fromList <$> maybe (pure [defCipherSuiteV7]) (traverse getOneCipherSuite) suites + suitesQuery <- Set.fromList <$> maybe (pure [legacyCipherSuite]) (traverse getOneCipherSuite) suites when (any isNothing suitesKPM) . void $ mlsProtocolError "uploaded key packages contains unsupported cipher suite" unless (suitesQuery == suitesKP) . void $ mlsProtocolError "uploaded key packages for unannounced cipher suites" pure suitesQuery From b2b9c76a8eabfb11540053a56393221627bfbeca Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Thu, 19 Dec 2024 09:19:45 +0000 Subject: [PATCH 10/12] require previously optional ciphersuite query param --- .../src/Wire/API/Routes/Public/Brig.hs | 78 ++++++++++++++++++- services/brig/src/Brig/API/MLS/KeyPackages.hs | 44 +++++++++-- services/brig/src/Brig/API/Public.hs | 4 + 3 files changed, 117 insertions(+), 9 deletions(-) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs index a554f2aeba9..763cb7afc73 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -1355,6 +1355,15 @@ type PropertiesAPI = -- MLS API --------------------------------------------------------------------- type CipherSuiteParam = + QueryParam' + [ Required, + Strict, + Description "Ciphersuite in hex format (e.g. 0xf031)" + ] + "ciphersuite" + CipherSuite + +type CipherSuiteParamV7 = QueryParam' [ Optional, Strict, @@ -1364,6 +1373,15 @@ type CipherSuiteParam = CipherSuite type MultipleCipherSuitesParam = + QueryParam' + [ Required, + Strict, + Description "Comma-separated list of ciphersuites in hex format (e.g. 0xf031)" + ] + "ciphersuites" + (CommaSeparatedList CipherSuite) + +type MultipleCipherSuitesParamV7 = QueryParam' [ Optional, Strict, @@ -1388,10 +1406,25 @@ type MLSKeyPackageAPI = :> MultiVerb 'POST '[JSON, MLS] '[RespondEmpty 201 "Key packages uploaded"] () ) :<|> Named - "mls-key-packages-replace" + "mls-key-packages-replace@v7" ( "self" :> Summary "Upload a fresh batch of key packages and replace the old ones" :> From 'V5 + :> Until 'V8 + :> Description "The request body should be a json object containing a list of base64-encoded key packages. Use this sparingly." + :> ZLocalUser + :> CanThrow 'MLSProtocolError + :> CanThrow 'MLSIdentityMismatch + :> CaptureClientId "client" + :> MultipleCipherSuitesParamV7 + :> ReqBody '[JSON] KeyPackageUpload + :> MultiVerb 'PUT '[JSON, MLS] '[RespondEmpty 201 "Key packages replaced"] () + ) + :<|> Named + "mls-key-packages-replace" + ( "self" + :> Summary "Upload a fresh batch of key packages and replace the old ones" + :> From 'V8 :> Description "The request body should be a json object containing a list of base64-encoded key packages. Use this sparingly." :> ZLocalUser :> CanThrow 'MLSProtocolError @@ -1402,22 +1435,47 @@ type MLSKeyPackageAPI = :> MultiVerb 'PUT '[JSON, MLS] '[RespondEmpty 201 "Key packages replaced"] () ) :<|> Named - "mls-key-packages-claim" + "mls-key-packages-claim@v7" ( "claim" :> Summary "Claim one key package for each client of the given user" :> From 'V5 + :> Until 'V8 :> Description "Only key packages for the specified ciphersuite are claimed. For backwards compatibility, the `ciphersuite` parameter is optional, defaulting to ciphersuite 0x0001 when omitted." :> ZLocalUser :> ZOptClient :> QualifiedCaptureUserId "user" + :> CipherSuiteParamV7 + :> MultiVerb1 'POST '[JSON] (Respond 200 "Claimed key packages" KeyPackageBundle) + ) + :<|> Named + "mls-key-packages-claim" + ( "claim" + :> Summary "Claim one key package for each client of the given user" + :> From 'V8 + :> Description "Only key packages for the specified ciphersuite are claimed." + :> ZLocalUser + :> ZOptClient + :> QualifiedCaptureUserId "user" :> CipherSuiteParam :> MultiVerb1 'POST '[JSON] (Respond 200 "Claimed key packages" KeyPackageBundle) ) :<|> Named - "mls-key-packages-count" + "mls-key-packages-count@v7" ( "self" :> Summary "Return the number of unclaimed key packages for a given ciphersuite and client" :> From 'V5 + :> Until 'V8 + :> ZLocalUser + :> CaptureClientId "client" + :> "count" + :> CipherSuiteParamV7 + :> MultiVerb1 'GET '[JSON] (Respond 200 "Number of key packages" KeyPackageCount) + ) + :<|> Named + "mls-key-packages-count" + ( "self" + :> Summary "Return the number of unclaimed key packages for a given ciphersuite and client" + :> From 'V8 :> ZLocalUser :> CaptureClientId "client" :> "count" @@ -1425,9 +1483,21 @@ type MLSKeyPackageAPI = :> MultiVerb1 'GET '[JSON] (Respond 200 "Number of key packages" KeyPackageCount) ) :<|> Named - "mls-key-packages-delete" + "mls-key-packages-delete@v7" ( "self" :> From 'V5 + :> Until 'V8 + :> ZLocalUser + :> CaptureClientId "client" + :> Summary "Delete all key packages for a given ciphersuite and client" + :> CipherSuiteParamV7 + :> ReqBody '[JSON] DeleteKeyPackages + :> MultiVerb1 'DELETE '[JSON] (RespondEmpty 201 "OK") + ) + :<|> Named + "mls-key-packages-delete" + ( "self" + :> From 'V8 :> ZLocalUser :> CaptureClientId "client" :> Summary "Delete all key packages for a given ciphersuite and client" diff --git a/services/brig/src/Brig/API/MLS/KeyPackages.hs b/services/brig/src/Brig/API/MLS/KeyPackages.hs index f0e96bc1576..0a87028bc93 100644 --- a/services/brig/src/Brig/API/MLS/KeyPackages.hs +++ b/services/brig/src/Brig/API/MLS/KeyPackages.hs @@ -18,10 +18,14 @@ module Brig.API.MLS.KeyPackages ( uploadKeyPackages, claimKeyPackages, + claimKeyPackagesV7, claimLocalKeyPackages, countKeyPackages, + countKeyPackagesV7, deleteKeyPackages, + deleteKeyPackagesV7, replaceKeyPackages, + replaceKeyPackagesV7, ) where @@ -65,6 +69,17 @@ uploadKeyPackages lusr cid kps = do lift . wrapClient $ Data.insertKeyPackages (tUnqualified lusr) cid kps' claimKeyPackages :: + ( Member GalleyAPIAccess r, + Member UserStore r + ) => + Local UserId -> + Maybe ClientId -> + Qualified UserId -> + CipherSuite -> + Handler r KeyPackageBundle +claimKeyPackages lusr mClient target = claimKeyPackagesV7 lusr mClient target . Just + +claimKeyPackagesV7 :: ( Member GalleyAPIAccess r, Member UserStore r ) => @@ -73,7 +88,7 @@ claimKeyPackages :: Qualified UserId -> Maybe CipherSuite -> Handler r KeyPackageBundle -claimKeyPackages lusr mClient target mSuite = do +claimKeyPackagesV7 lusr mClient target mSuite = do assertMLSEnabled suite <- getCipherSuite mSuite @@ -180,8 +195,11 @@ claimRemoteKeyPackages lusr suite target = do handleFailure :: (Monad m) => Maybe x -> ExceptT ClientError m x handleFailure = maybe (throwE (ClientUserNotFound (tUnqualified target))) pure -countKeyPackages :: Local UserId -> ClientId -> Maybe CipherSuite -> Handler r KeyPackageCount -countKeyPackages lusr c mSuite = do +countKeyPackages :: Local UserId -> ClientId -> CipherSuite -> Handler r KeyPackageCount +countKeyPackages lusr c = countKeyPackagesV7 lusr c . Just + +countKeyPackagesV7 :: Local UserId -> ClientId -> Maybe CipherSuite -> Handler r KeyPackageCount +countKeyPackagesV7 lusr c mSuite = do assertMLSEnabled suite <- getCipherSuite mSuite lift $ @@ -189,23 +207,39 @@ countKeyPackages lusr c mSuite = do <$> wrapClient (Data.countKeyPackages lusr c suite) deleteKeyPackages :: + Local UserId -> + ClientId -> + CipherSuite -> + DeleteKeyPackages -> + Handler r () +deleteKeyPackages lusr c = deleteKeyPackagesV7 lusr c . Just + +deleteKeyPackagesV7 :: Local UserId -> ClientId -> Maybe CipherSuite -> DeleteKeyPackages -> Handler r () -deleteKeyPackages lusr c mSuite (unDeleteKeyPackages -> refs) = do +deleteKeyPackagesV7 lusr c mSuite (unDeleteKeyPackages -> refs) = do assertMLSEnabled suite <- getCipherSuite mSuite lift $ wrapClient (Data.deleteKeyPackages (tUnqualified lusr) c suite refs) replaceKeyPackages :: + Local UserId -> + ClientId -> + CommaSeparatedList CipherSuite -> + KeyPackageUpload -> + Handler r () +replaceKeyPackages lusr c = replaceKeyPackagesV7 lusr c . Just + +replaceKeyPackagesV7 :: Local UserId -> ClientId -> Maybe (CommaSeparatedList CipherSuite) -> KeyPackageUpload -> Handler r () -replaceKeyPackages lusr c (fmap toList -> mSuites) upload = do +replaceKeyPackagesV7 lusr c (fmap toList -> mSuites) upload = do assertMLSEnabled suites <- validateCipherSuites mSuites upload lift $ wrapClient (Data.deleteAllKeyPackages (tUnqualified lusr) c suites) diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index d48ce89a83c..7ce1de25e31 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -500,9 +500,13 @@ servantSitemap = mlsAPI :: ServerT MLSAPI (Handler r) mlsAPI = Named @"mls-key-packages-upload" uploadKeyPackages + :<|> Named @"mls-key-packages-replace@v7" replaceKeyPackagesV7 :<|> Named @"mls-key-packages-replace" replaceKeyPackages + :<|> Named @"mls-key-packages-claim@v7" claimKeyPackagesV7 :<|> Named @"mls-key-packages-claim" claimKeyPackages + :<|> Named @"mls-key-packages-count@v7" countKeyPackagesV7 :<|> Named @"mls-key-packages-count" countKeyPackages + :<|> Named @"mls-key-packages-delete@v7" deleteKeyPackagesV7 :<|> Named @"mls-key-packages-delete" deleteKeyPackages userHandleAPI :: ServerT UserHandleAPI (Handler r) From 2fd8c2dacc91077520ce42c9c50f5ae5cba6a26a Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Thu, 19 Dec 2024 11:23:32 +0000 Subject: [PATCH 11/12] fixup! require previously optional ciphersuite query param --- integration/test/API/Brig.hs | 14 ++- integration/test/Test/MLS/KeyPackage.hs | 113 ++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/integration/test/API/Brig.hs b/integration/test/API/Brig.hs index 1825d27792b..fad55b67595 100644 --- a/integration/test/API/Brig.hs +++ b/integration/test/API/Brig.hs @@ -366,11 +366,21 @@ deleteKeyPackages suite cid kps = do & addQueryParams [("ciphersuite", suite.code)] & addJSONObject ["key_packages" .= kps] -replaceKeyPackages :: ClientIdentity -> Maybe [Ciphersuite] -> [ByteString] -> App Response -replaceKeyPackages cid mSuites kps = do +replaceKeyPackages :: ClientIdentity -> [Ciphersuite] -> [ByteString] -> App Response +replaceKeyPackages cid suites kps = do req <- baseRequest cid Brig Versioned $ "/mls/key-packages/self/" <> cid.client + submit "PUT" $ + req + & addQueryParams [("ciphersuites", intercalate "," (map (.code) suites))] + & addJSONObject ["key_packages" .= map (T.decodeUtf8 . Base64.encode) kps] + +replaceKeyPackagesV7 :: ClientIdentity -> Maybe [Ciphersuite] -> [ByteString] -> App Response +replaceKeyPackagesV7 cid mSuites kps = do + req <- + baseRequest cid Brig (ExplicitVersion 7) $ + "/mls/key-packages/self/" <> cid.client submit "PUT" $ req & maybe id (\suites -> addQueryParams [("ciphersuites", intercalate "," (map (.code) suites))]) mSuites diff --git a/integration/test/Test/MLS/KeyPackage.hs b/integration/test/Test/MLS/KeyPackage.hs index df51afe2237..667d8ad913e 100644 --- a/integration/test/Test/MLS/KeyPackage.hs +++ b/integration/test/Test/MLS/KeyPackage.hs @@ -208,6 +208,109 @@ testUnsupportedCiphersuite = do testReplaceKeyPackages :: (HasCallStack) => App () testReplaceKeyPackages = do + let suite = def + altSuite = Ciphersuite "0x0007" + alice <- randomUser OwnDomain def + [alice1, alice2] <- replicateM 2 $ createMLSClientWithCiphersuites [suite, altSuite] def alice + + let checkCount :: (HasCallStack) => Ciphersuite -> Int -> App () + checkCount cs n = + bindResponse (countKeyPackages cs alice1) $ \resp -> do + resp.status `shouldMatchInt` 200 + resp.json %. "count" `shouldMatchInt` n + + -- setup: upload a batch of key packages for each ciphersuite + void + $ replicateM 4 (fmap fst (generateKeyPackage alice1 suite)) + >>= uploadKeyPackages alice1 + >>= getBody 201 + void + $ replicateM 5 (fmap fst (generateKeyPackage alice1 altSuite)) + >>= uploadKeyPackages alice1 + >>= getBody 201 + + checkCount suite 4 + checkCount altSuite 5 + + do + -- generate a new batch of key packages + (kps, refs) <- unzip <$> replicateM 3 (generateKeyPackage alice1 altSuite) + + -- replace old key packages with new + void $ replaceKeyPackages alice1 [altSuite] kps >>= getBody 201 + + checkCount suite 4 + checkCount altSuite 3 + + -- claim all key packages one by one + claimed <- + replicateM 3 + $ bindResponse (claimKeyPackages altSuite alice2 alice) + $ \resp -> do + resp.status `shouldMatchInt` 200 + ks <- resp.json %. "key_packages" & asList + k <- assertOne ks + k %. "key_package_ref" + + refs `shouldMatchSet` claimed + + checkCount suite 4 + checkCount altSuite 0 + + do + -- replenish key packages for the second ciphersuite + void + $ replicateM 5 (fmap fst (generateKeyPackage alice1 altSuite)) + >>= uploadKeyPackages alice1 + >>= getBody 201 + + checkCount suite 4 + checkCount altSuite 5 + + -- replace all key packages with fresh ones + kps1 <- replicateM 3 (fmap fst (generateKeyPackage alice1 suite)) + kps2 <- replicateM 2 (fmap fst (generateKeyPackage alice1 altSuite)) + + void $ replaceKeyPackages alice1 [suite, altSuite] (kps1 <> kps2) >>= getBody 201 + + checkCount suite 3 + checkCount altSuite 2 + + do + suiteKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 suite)) + altSuiteKeyPackages <- replicateM 3 (fmap fst (generateKeyPackage alice1 altSuite)) + + void + $ replaceKeyPackages alice1 [] [] + `bindResponse` \resp -> do + resp.status `shouldMatchInt` 201 + + checkCount suite 3 + checkCount altSuite 2 + + let testErrorCases :: (HasCallStack) => [Ciphersuite] -> [ByteString] -> App () + testErrorCases ciphersuites keyPackages = do + void + $ replaceKeyPackages alice1 ciphersuites keyPackages + `bindResponse` \resp -> do + resp.status `shouldMatchInt` 400 + resp.json %. "label" `shouldMatch` "mls-protocol-error" + checkCount suite 3 + checkCount altSuite 2 + + testErrorCases [] suiteKeyPackages + testErrorCases [] altSuiteKeyPackages + + testErrorCases [altSuite] suiteKeyPackages + testErrorCases [altSuite] (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases [altSuite] [] + + testErrorCases [suite] altSuiteKeyPackages + testErrorCases [suite] (altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases [suite] [] + +testReplaceKeyPackagesV7 :: (HasCallStack) => App () +testReplaceKeyPackagesV7 = do let suite = def altSuite = Ciphersuite "0x0007" oldSuite = Ciphersuite "0x0001" @@ -243,7 +346,7 @@ testReplaceKeyPackages = do (kps, refs) <- unzip <$> replicateM 3 (generateKeyPackage alice1 altSuite) -- replace old key packages with new - void $ replaceKeyPackages alice1 (Just [altSuite]) kps >>= getBody 201 + void $ replaceKeyPackagesV7 alice1 (Just [altSuite]) kps >>= getBody 201 checkCount suite 4 checkCount altSuite 3 @@ -277,7 +380,7 @@ testReplaceKeyPackages = do kps1 <- replicateM 3 (fmap fst (generateKeyPackage alice1 suite)) kps2 <- replicateM 2 (fmap fst (generateKeyPackage alice1 altSuite)) - void $ replaceKeyPackages alice1 (Just [suite, altSuite]) (kps1 <> kps2) >>= getBody 201 + void $ replaceKeyPackagesV7 alice1 (Just [suite, altSuite]) (kps1 <> kps2) >>= getBody 201 checkCount suite 3 checkCount altSuite 2 @@ -288,12 +391,12 @@ testReplaceKeyPackages = do oldSuiteKeyPackages <- replicateM 4 (fmap fst (generateKeyPackage alice1 oldSuite)) void - $ replaceKeyPackages alice1 (Just []) [] + $ replaceKeyPackagesV7 alice1 (Just []) [] `bindResponse` \resp -> do resp.status `shouldMatchInt` 201 void - $ replaceKeyPackages alice1 Nothing oldSuiteKeyPackages + $ replaceKeyPackagesV7 alice1 Nothing oldSuiteKeyPackages `bindResponse` \resp -> do resp.status `shouldMatchInt` 201 @@ -304,7 +407,7 @@ testReplaceKeyPackages = do let testErrorCases :: (HasCallStack) => Maybe [Ciphersuite] -> [ByteString] -> App () testErrorCases ciphersuites keyPackages = do void - $ replaceKeyPackages alice1 ciphersuites keyPackages + $ replaceKeyPackagesV7 alice1 ciphersuites keyPackages `bindResponse` \resp -> do resp.status `shouldMatchInt` 400 resp.json %. "label" `shouldMatch` "mls-protocol-error" From abcdd74db6806e5ed904db87e1c2fb8358460422 Mon Sep 17 00:00:00 2001 From: Stefan Berthold Date: Thu, 19 Dec 2024 14:34:34 +0000 Subject: [PATCH 12/12] fixup! fixup! require previously optional ciphersuite query param --- services/brig/test/integration/API/MLS/Util.hs | 1 + services/brig/test/integration/Federation/End2end.hs | 1 + services/galley/test/integration/API/MLS/Util.hs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/services/brig/test/integration/API/MLS/Util.hs b/services/brig/test/integration/API/MLS/Util.hs index 445ca6875fb..4607c70abce 100644 --- a/services/brig/test/integration/API/MLS/Util.hs +++ b/services/brig/test/integration/API/MLS/Util.hs @@ -125,6 +125,7 @@ getKeyPackageCount brig u c = =<< get ( brig . paths ["mls", "key-packages", "self", toByteString' c, "count"] + . queryItem "ciphersuite" "0x0001" . zUser (qUnqualified u) )