Skip to content

Commit 9830a3e

Browse files
author
Marko Dimjašević
authored
[FS-937] Validate Remotely Claimed Key Packages (#2692)
* Provide a custom Show instance for ClientIdentity * Align function types in KeyPackages and Validation * Validate remotely claimed key packages * Restrict validation of MLS public keys to locals * Update Brig integration test utils * Update the remote key package claim test
1 parent fe819ae commit 9830a3e

File tree

9 files changed

+135
-52
lines changed

9 files changed

+135
-52
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Validate remotely claimed key packages

libs/wire-api/src/Wire/API/Error/Brig.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ data BrigError
6464
| PasswordAuthenticationFailed
6565
| TooManyTeamInvitations
6666
| InsufficientTeamPermissions
67+
| KeyPackageDecodingError
68+
| InvalidKeyPackageRef
6769

6870
instance KnownError (MapError e) => IsSwaggerError (e :: BrigError) where
6971
addToSwagger = addStaticErrorToSwagger @(MapError e)
@@ -172,3 +174,7 @@ type instance MapError 'PasswordAuthenticationFailed = 'StaticError 403 "passwor
172174
type instance MapError 'TooManyTeamInvitations = 'StaticError 403 "too-many-team-invitations" "Too many team invitations for this team"
173175

174176
type instance MapError 'InsufficientTeamPermissions = 'StaticError 403 "insufficient-permissions" "Insufficient team permissions"
177+
178+
type instance MapError 'KeyPackageDecodingError = 'StaticError 409 "decoding-error" "Key package could not be TLS-decoded"
179+
180+
type instance MapError 'InvalidKeyPackageRef = 'StaticError 409 "invalid-reference" "Key package's reference does not match its data"

libs/wire-api/src/Wire/API/MLS/Credential.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,17 @@ data ClientIdentity = ClientIdentity
139139
ciUser :: UserId,
140140
ciClient :: ClientId
141141
}
142-
deriving stock (Eq, Ord, Show, Generic)
142+
deriving stock (Eq, Ord, Generic)
143143
deriving (FromJSON, ToJSON, S.ToSchema) via Schema ClientIdentity
144144

145+
instance Show ClientIdentity where
146+
show (ClientIdentity dom u c) =
147+
show u
148+
<> ":"
149+
<> T.unpack (client c)
150+
<> "@"
151+
<> T.unpack (domainText dom)
152+
145153
cidQualifiedClient :: ClientIdentity -> Qualified (UserId, ClientId)
146154
cidQualifiedClient cid = Qualified (ciUser cid, ciClient cid) (ciDomain cid)
147155

services/brig/src/Brig/API/Error.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ clientDataError (ClientReAuthError e) = reauthError e
182182
clientDataError ClientMissingAuth = StdError (errorToWai @'E.MissingAuth)
183183
clientDataError MalformedPrekeys = StdError (errorToWai @'E.MalformedPrekeys)
184184
clientDataError MLSPublicKeyDuplicate = StdError (errorToWai @'E.MLSDuplicatePublicKey)
185+
clientDataError KeyPackageDecodingError = StdError (errorToWai @'E.KeyPackageDecodingError)
186+
clientDataError InvalidKeyPackageRef = StdError (errorToWai @'E.InvalidKeyPackageRef)
185187

186188
deleteUserError :: DeleteUserError -> Error
187189
deleteUserError DeleteUserInvalid = StdError (errorToWai @'E.InvalidUser)

services/brig/src/Brig/API/MLS/KeyPackages.hs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Wire.API.Federation.API
4242
import Wire.API.Federation.API.Brig
4343
import Wire.API.MLS.Credential
4444
import Wire.API.MLS.KeyPackage
45+
import Wire.API.MLS.Serialisation
4546
import Wire.API.Team.LegalHold
4647
import Wire.API.User.Client
4748

@@ -57,12 +58,11 @@ claimKeyPackages ::
5758
Maybe ClientId ->
5859
Handler r KeyPackageBundle
5960
claimKeyPackages lusr target skipOwn =
60-
withExceptT clientError $
61-
foldQualified
62-
lusr
63-
(claimLocalKeyPackages (qUntagged lusr) skipOwn)
64-
(claimRemoteKeyPackages lusr)
65-
target
61+
foldQualified
62+
lusr
63+
(withExceptT clientError . claimLocalKeyPackages (qUntagged lusr) skipOwn)
64+
(claimRemoteKeyPackages lusr)
65+
target
6666

6767
claimLocalKeyPackages ::
6868
Qualified UserId ->
@@ -96,22 +96,35 @@ claimLocalKeyPackages qusr skipOwn target = do
9696
claimRemoteKeyPackages ::
9797
Local UserId ->
9898
Remote UserId ->
99-
ExceptT ClientError (AppT r) KeyPackageBundle
99+
Handler r KeyPackageBundle
100100
claimRemoteKeyPackages lusr target = do
101101
bundle <-
102-
(handleFailure =<<) $
103-
withExceptT ClientFederationError $
102+
withExceptT clientError
103+
. (handleFailure =<<)
104+
$ withExceptT ClientFederationError $
104105
runBrigFederatorClient (tDomain target) $
105106
fedClient @'Brig @"claim-key-packages" $
106107
ClaimKeyPackageRequest
107108
{ ckprClaimant = tUnqualified lusr,
108109
ckprTarget = tUnqualified target
109110
}
110111

111-
-- set up mappings for all claimed key packages
112-
wrapClientE $
113-
for_ (kpbEntries bundle) $ \e ->
114-
Data.mapKeyPackageRef (kpbeRef e) (kpbeUser e) (kpbeClient e)
112+
-- validate and set up mappings for all claimed key packages
113+
for_ (kpbEntries bundle) $ \e -> do
114+
let cid = mkClientIdentity (kpbeUser e) (kpbeClient e)
115+
kpRaw <-
116+
withExceptT (const . clientDataError $ KeyPackageDecodingError)
117+
. except
118+
. decodeMLS'
119+
. kpData
120+
. kpbeKeyPackage
121+
$ e
122+
(refVal, _) <- validateKeyPackage cid kpRaw
123+
unless (refVal == kpbeRef e)
124+
. throwE
125+
. clientDataError
126+
$ InvalidKeyPackageRef
127+
wrapClientE $ Data.mapKeyPackageRef (kpbeRef e) (kpbeUser e) (kpbeClient e)
115128

116129
pure bundle
117130
where

services/brig/src/Brig/API/MLS/KeyPackages/Validation.hs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import Brig.Options
3535
import Control.Applicative
3636
import Control.Lens (view)
3737
import qualified Data.ByteString.Lazy as LBS
38+
import Data.Qualified
3839
import Data.Time.Clock
3940
import Data.Time.Clock.POSIX
4041
import Imports
@@ -46,8 +47,12 @@ import Wire.API.MLS.Extension
4647
import Wire.API.MLS.KeyPackage
4748
import Wire.API.MLS.Serialisation
4849

49-
validateKeyPackage :: ClientIdentity -> RawMLS KeyPackage -> Handler r (KeyPackageRef, KeyPackageData)
50+
validateKeyPackage ::
51+
ClientIdentity ->
52+
RawMLS KeyPackage ->
53+
Handler r (KeyPackageRef, KeyPackageData)
5054
validateKeyPackage identity (RawMLS (KeyPackageData -> kpd) kp) = do
55+
loc <- qualifyLocal ()
5156
-- get ciphersuite
5257
cs <-
5358
maybe
@@ -60,19 +65,32 @@ validateKeyPackage identity (RawMLS (KeyPackageData -> kpd) kp) = do
6065
when (signatureScheme ss /= bcSignatureScheme (kpCredential kp)) $
6166
mlsProtocolError "Signature scheme incompatible with ciphersuite"
6267

63-
-- authenticate signature key
64-
key <-
65-
fmap LBS.toStrict $
66-
maybe
67-
(mlsProtocolError "No key associated to the given identity and signature scheme")
68-
pure
69-
=<< lift (wrapClient (Data.lookupMLSPublicKey (ciUser identity) (ciClient identity) ss))
70-
when (key /= bcSignatureKey (kpCredential kp)) $
71-
mlsProtocolError "Unrecognised signature key"
68+
-- Authenticate signature key. This is performed only upon uploading a key
69+
-- package for a local client.
70+
foldQualified
71+
loc
72+
( \_ -> do
73+
key <-
74+
fmap LBS.toStrict $
75+
maybe
76+
(mlsProtocolError "No key associated to the given identity and signature scheme")
77+
pure
78+
=<< lift (wrapClient (Data.lookupMLSPublicKey (ciUser identity) (ciClient identity) ss))
79+
when (key /= bcSignatureKey (kpCredential kp)) $
80+
mlsProtocolError "Unrecognised signature key"
81+
)
82+
(pure . const ())
83+
(cidQualifiedClient identity)
7284

7385
-- validate signature
74-
unless (csVerifySignature cs key (rmRaw (kpTBS kp)) (kpSignature kp)) $
75-
mlsProtocolError "Invalid signature"
86+
unless
87+
( csVerifySignature
88+
cs
89+
(bcSignatureKey (kpCredential kp))
90+
(rmRaw (kpTBS kp))
91+
(kpSignature kp)
92+
)
93+
$ mlsProtocolError "Invalid signature"
7694
-- validate protocol version
7795
maybe
7896
(mlsProtocolError "Unsupported protocol version")

services/brig/src/Brig/Data/Client.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ data ClientDataError
9797
| ClientMissingAuth
9898
| MalformedPrekeys
9999
| MLSPublicKeyDuplicate
100+
| KeyPackageDecodingError
101+
| InvalidKeyPackageRef
100102

101103
-- | Re-authentication policy.
102104
--

services/brig/test/integration/API/MLS.hs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import Bilge.Assert
2323
import Brig.Options
2424
import Control.Timeout
2525
import qualified Data.Aeson as Aeson
26-
import qualified Data.ByteString as BS
2726
import Data.ByteString.Conversion
2827
import Data.Default
2928
import Data.Id
@@ -32,14 +31,14 @@ import qualified Data.Set as Set
3231
import Data.Timeout
3332
import Federation.Util
3433
import Imports
35-
import Test.QuickCheck hiding ((===))
3634
import Test.Tasty
3735
import Test.Tasty.HUnit
3836
import UnliftIO.Temporary
3937
import Util
4038
import Web.HttpApiData
4139
import Wire.API.MLS.Credential
4240
import Wire.API.MLS.KeyPackage
41+
import Wire.API.MLS.Serialisation
4342
import Wire.API.User
4443
import Wire.API.User.Client
4544

@@ -186,13 +185,18 @@ testKeyPackageRemoteClaim opts brig = do
186185

187186
u' <- userQualifiedId <$> randomUser brig
188187

189-
entries <-
190-
liftIO . replicateM 2 . generate $
191-
-- claimed key packages are not validated by the backend, so it is fine to
192-
-- make up some random data here
193-
KeyPackageBundleEntry u <$> arbitrary
194-
<*> (KeyPackageRef . BS.pack <$> vector 32)
195-
<*> (KeyPackageData . BS.pack <$> vector 64)
188+
qcid <- mkClientIdentity u <$> randomClient
189+
entries <- withSystemTempDirectory "mls" $ \tmp -> do
190+
initStore tmp qcid
191+
replicateM 2 $ do
192+
(r, kp) <- generateKeyPackage tmp qcid Nothing
193+
pure $
194+
KeyPackageBundleEntry
195+
{ kpbeUser = u,
196+
kpbeClient = ciClient qcid,
197+
kpbeRef = kp,
198+
kpbeKeyPackage = KeyPackageData . rmRaw $ r
199+
}
196200
let mockBundle = KeyPackageBundle (Set.fromList entries)
197201
(bundle :: KeyPackageBundle, _reqs) <-
198202
liftIO . withTempMockFederator opts (Aeson.encode mockBundle) $

services/brig/test/integration/API/MLS/Util.hs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@ import Bilge.Assert
2323
import Data.Aeson (object, toJSON, (.=))
2424
import Data.ByteString.Conversion
2525
import Data.Default
26-
import Data.Domain
2726
import Data.Id
2827
import Data.Json.Util
2928
import qualified Data.Map as Map
3029
import Data.Qualified
31-
import qualified Data.Text as T
30+
import qualified Data.Text as Text
3231
import Data.Timeout
3332
import Imports
3433
import System.FilePath
3534
import System.Process
35+
import Test.Tasty.HUnit
3636
import Util
3737
import Wire.API.MLS.Credential
3838
import Wire.API.MLS.KeyPackage
39+
import Wire.API.MLS.Serialisation
3940
import Wire.API.User.Client
4041

4142
data SetKey = SetKey | DontSetKey
@@ -49,6 +50,39 @@ data KeyingInfo = KeyingInfo
4950
instance Default KeyingInfo where
5051
def = KeyingInfo SetKey Nothing
5152

53+
cliCmd :: FilePath -> ClientIdentity -> [String]
54+
cliCmd tmp qcid =
55+
["mls-test-cli", "--store", tmp </> (show qcid <> ".db")]
56+
57+
initStore ::
58+
HasCallStack =>
59+
MonadIO m =>
60+
FilePath ->
61+
ClientIdentity ->
62+
m ()
63+
initStore tmp qcid = do
64+
let cmd0 = cliCmd tmp qcid
65+
void . liftIO . flip spawn Nothing . shell . unwords $
66+
cmd0 <> ["init", show qcid]
67+
68+
generateKeyPackage ::
69+
HasCallStack =>
70+
MonadIO m =>
71+
FilePath ->
72+
ClientIdentity ->
73+
Maybe Timeout ->
74+
m (RawMLS KeyPackage, KeyPackageRef)
75+
generateKeyPackage tmp qcid lifetime = do
76+
let cmd0 = cliCmd tmp qcid
77+
kp <-
78+
liftIO $
79+
decodeMLSError <=< (flip spawn Nothing . shell . unwords) $
80+
cmd0
81+
<> ["key-package", "create"]
82+
<> (("--lifetime " <>) . show . (#> Second) <$> maybeToList lifetime)
83+
let ref = fromJust (kpRef' kp)
84+
pure (kp, ref)
85+
5286
uploadKeyPackages ::
5387
HasCallStack =>
5488
Brig ->
@@ -59,20 +93,10 @@ uploadKeyPackages ::
5993
Int ->
6094
Http ()
6195
uploadKeyPackages brig tmp KeyingInfo {..} u c n = do
62-
let cmd0 = ["mls-test-cli", "--store", tmp </> (clientId <> ".db")]
63-
clientId =
64-
show (qUnqualified u)
65-
<> ":"
66-
<> T.unpack (client c)
67-
<> "@"
68-
<> T.unpack (domainText (qDomain u))
69-
void . liftIO . flip spawn Nothing . shell . unwords $
70-
cmd0 <> ["init", clientId]
71-
kps <-
72-
replicateM n . liftIO . flip spawn Nothing . shell . unwords $
73-
cmd0
74-
<> ["key-package", "create"]
75-
<> (("--lifetime " <>) . show . (#> Second) <$> maybeToList kiLifetime)
96+
let cmd0 = cliCmd tmp cid
97+
cid = mkClientIdentity u c
98+
initStore tmp cid
99+
kps <- replicateM n (fst <$> generateKeyPackage tmp cid kiLifetime)
76100
when (kiSetKey == SetKey) $
77101
do
78102
pk <-
@@ -85,7 +109,7 @@ uploadKeyPackages brig tmp KeyingInfo {..} u c n = do
85109
. json defUpdateClient {updateClientMLSPublicKeys = Map.fromList [(Ed25519, pk)]}
86110
)
87111
!!! const 200 === statusCode
88-
let upload = object ["key_packages" .= toJSON (map Base64ByteString kps)]
112+
let upload = object ["key_packages" .= toJSON (map (Base64ByteString . rmRaw) kps)]
89113
post
90114
( brig
91115
. paths ["mls", "key-packages", "self", toByteString' c]
@@ -102,3 +126,8 @@ getKeyPackageCount brig u c =
102126
. zUser (qUnqualified u)
103127
)
104128
<!! const 200 === statusCode
129+
130+
decodeMLSError :: ParseMLS a => ByteString -> IO a
131+
decodeMLSError s = case decodeMLS' s of
132+
Left e -> assertFailure ("Could not parse MLS object: " <> Text.unpack e)
133+
Right x -> pure x

0 commit comments

Comments
 (0)