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! 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/API/Brig.hs b/integration/test/API/Brig.hs index b788d3c6f74..fad55b67595 100644 --- a/integration/test/API/Brig.hs +++ b/integration/test/API/Brig.hs @@ -358,16 +358,29 @@ 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 +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/MLS/Util.hs b/integration/test/MLS/Util.hs index e70fa74d259..6814757281a 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 @@ -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/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/KeyPackage.hs b/integration/test/Test/MLS/KeyPackage.hs index 2c49f0206d7..667d8ad913e 100644 --- a/integration/test/Test/MLS/KeyPackage.hs +++ b/integration/test/Test/MLS/KeyPackage.hs @@ -18,7 +18,7 @@ testDeleteKeyPackages = do -- 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 @@ -27,26 +27,27 @@ testDeleteKeyPackages = do testKeyPackageMultipleCiphersuites :: App () testKeyPackageMultipleCiphersuites = do + let suite = def + altSuite = Ciphersuite "0x0007" alice <- randomUser OwnDomain def - [alice1, alice2] <- replicateM 2 (createMLSClient def def alice) + [alice1, alice2] <- replicateM 2 (createMLSClientWithCiphersuites [suite, altSuite] def alice) - kp <- uploadNewKeyPackage def alice2 + kp <- uploadNewKeyPackage suite alice2 - let suite = Ciphersuite "0xf031" - void $ uploadNewKeyPackage suite alice2 + 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,42 +208,153 @@ testUnsupportedCiphersuite = do testReplaceKeyPackages :: (HasCallStack) => App () testReplaceKeyPackages = do + let suite = def + altSuite = Ciphersuite "0x0007" alice <- randomUser OwnDomain def - [alice1, alice2] <- replicateM 2 $ createMLSClient def def alice - let suite = Ciphersuite "0xf031" + [alice1, alice2] <- replicateM 2 $ createMLSClientWithCiphersuites [suite, altSuite] 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 -- 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 [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 def 4 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" + alice <- randomUser OwnDomain def + [alice1, alice2] <- replicateM 2 $ createMLSClientWithCiphersuites [suite, altSuite, oldSuite] 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 + 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 + (kps, refs) <- unzip <$> replicateM 3 (generateKeyPackage alice1 altSuite) + + -- replace old key packages with new + void $ replaceKeyPackagesV7 alice1 (Just [altSuite]) kps >>= getBody 201 + + 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,65 +363,68 @@ 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 3 (fmap fst (generateKeyPackage alice1 suite)) + kps2 <- replicateM 2 (fmap fst (generateKeyPackage alice1 altSuite)) - void $ replaceKeyPackages alice1 (Just [def, suite]) (kps1 <> kps2) >>= getBody 201 + void $ replaceKeyPackagesV7 alice1 (Just [suite, altSuite]) (kps1 <> kps2) >>= getBody 201 - checkCount def 2 - checkCount suite 2 + checkCount suite 3 + 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)) + 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 defKeyPackages + $ replaceKeyPackagesV7 alice1 Nothing oldSuiteKeyPackages `bindResponse` \resp -> do resp.status `shouldMatchInt` 201 - checkCount def 3 - checkCount suite 2 + checkCount suite 3 + checkCount altSuite 2 + checkCount oldSuite 4 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" - checkCount def 3 - checkCount suite 2 + checkCount suite 3 + checkCount altSuite 2 + checkCount oldSuite 4 - testErrorCases (Just []) defKeyPackages testErrorCases (Just []) suiteKeyPackages + testErrorCases (Just []) altSuiteKeyPackages testErrorCases Nothing [] - testErrorCases Nothing suiteKeyPackages - testErrorCases Nothing (suiteKeyPackages <> defKeyPackages) + testErrorCases Nothing altSuiteKeyPackages + testErrorCases Nothing (oldSuiteKeyPackages <> altSuiteKeyPackages <> suiteKeyPackages) - testErrorCases (Just [suite]) defKeyPackages - testErrorCases (Just [suite]) (suiteKeyPackages <> defKeyPackages) - testErrorCases (Just [suite]) [] + testErrorCases (Just [altSuite]) suiteKeyPackages + testErrorCases (Just [altSuite]) (oldSuiteKeyPackages <> altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases (Just [altSuite]) [] - testErrorCases (Just [def]) suiteKeyPackages - testErrorCases (Just [def]) (suiteKeyPackages <> defKeyPackages) - testErrorCases (Just [def]) [] + testErrorCases (Just [suite]) altSuiteKeyPackages + testErrorCases (Just [suite]) (oldSuiteKeyPackages <> altSuiteKeyPackages <> suiteKeyPackages) + testErrorCases (Just [suite]) [] diff --git a/integration/test/Test/MLS/One2One.hs b/integration/test/Test/MLS/One2One.hs index 660368b7660..bac143fcef9 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 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/src/Wire/API/MLS/CipherSuite.hs b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs index 5f93d01f8c3..a9703d6decb 100644 --- a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs +++ b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs @@ -20,7 +20,7 @@ module Wire.API.MLS.CipherSuite ( -- * MLS ciphersuites CipherSuite (..), - defCipherSuite, + legacyCipherSuite, CipherSuiteTag (..), cipherSuiteTag, tagCipherSuite, @@ -121,8 +121,11 @@ data CipherSuiteTag deriving stock (Bounded, Enum, Eq, Show, Generic, Ord) deriving (Arbitrary) via (GenericUniform CipherSuiteTag) -defCipherSuite :: CipherSuiteTag -defCipherSuite = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 +-- | 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. +legacyCipherSuite :: CipherSuiteTag +legacyCipherSuite = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 instance S.ToSchema CipherSuiteTag where declareNamedSchema _ = 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/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/services/brig/src/Brig/API/MLS/CipherSuite.hs b/services/brig/src/Brig/API/MLS/CipherSuite.hs index c47bd0fedaa..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 defCipherSuite) getOneCipherSuite +getCipherSuite = maybe (pure legacyCipherSuite) 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 [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 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) 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) )