passive_client_test.go raw
1 package mls
2
3 import (
4 "bytes"
5 "fmt"
6 "testing"
7 "time"
8 )
9
10 type passiveClientTest struct {
11 CipherSuite CipherSuite `json:"cipher_suite"`
12
13 ExternalPSKs []struct {
14 PSKID testBytes `json:"psk_id"`
15 PSK testBytes `json:"psk"`
16 } `json:"external_psks"`
17 KeyPackage testBytes `json:"key_package"`
18 SignaturePriv testBytes `json:"signature_priv"`
19 EncryptionPriv testBytes `json:"encryption_priv"`
20 InitPriv testBytes `json:"init_priv"`
21
22 Welcome testBytes `json:"welcome"`
23 RatchetTree testBytes `json:"ratchet_tree"`
24 InitialEpochAuthenticator testBytes `json:"initial_epoch_authenticator"`
25
26 Epochs []struct {
27 Proposals []testBytes `json:"proposals"`
28 Commit testBytes `json:"commit"`
29 EpochAuthenticator testBytes `json:"epoch_authenticator"`
30 } `json:"epochs"`
31 }
32
33 func testPassiveClient(t *testing.T, tc *passiveClientTest) {
34 cs := tc.CipherSuite
35 initPriv := []byte(tc.InitPriv)
36 encryptionPriv := []byte(tc.EncryptionPriv)
37 signaturePriv := []byte(tc.SignaturePriv)
38
39 if !cs.Supported() {
40 t.Skipf("unsupported cipher suite %v", cs)
41 }
42
43 welcome, err := UnmarshalWelcome(tc.Welcome)
44 if err != nil {
45 t.Fatalf("UnmarshalWelcome() = %v", err)
46 }
47 if welcome.cipherSuite != cs {
48 t.Fatalf("welcome.cipherSuite = %v, want %v", welcome.cipherSuite, cs)
49 }
50
51 keyPkg, err := UnmarshalKeyPackage([]byte(tc.KeyPackage))
52 if err != nil {
53 t.Fatalf("UnmarshalKeyPackage() = %v", err)
54 }
55 if keyPkg.cipherSuite != welcome.cipherSuite {
56 t.Fatalf("keyPkg.cipherSuite = %v, want %v", keyPkg.cipherSuite, welcome.cipherSuite)
57 }
58
59 if err := checkEncryptionKeyPair(cs, keyPkg.initKey, initPriv); err != nil {
60 t.Errorf("invalid init keypair: %v", err)
61 }
62 if err := checkEncryptionKeyPair(cs, keyPkg.leafNode.encryptionKey, encryptionPriv); err != nil {
63 t.Errorf("invalid encryption keypair: %v", err)
64 }
65 if err := checkSignatureKeyPair(cs, []byte(keyPkg.leafNode.signatureKey), signaturePriv); err != nil {
66 t.Errorf("invalid signature keypair: %v", err)
67 }
68
69 keyPkgRef, err := keyPkg.GenerateRef()
70 if err != nil {
71 t.Fatalf("keyPackage.generateRef() = %v", err)
72 }
73
74 groupSecrets, err := welcome.decryptGroupSecrets(keyPkgRef, initPriv)
75 if err != nil {
76 t.Fatalf("welcome.decryptGroupSecrets() = %v", err)
77 }
78
79 if !groupSecrets.verifySingleReinitOrBranchPSK() {
80 t.Errorf("groupSecrets.verifySingleReinitOrBranchPSK() failed")
81 }
82
83 var psks [][]byte
84 for _, pskID := range groupSecrets.psks {
85 if pskID.pskType != pskTypeExternal {
86 t.Fatalf("group secrets contain a non-external PSK ID")
87 }
88
89 found := false
90 for _, epsk := range tc.ExternalPSKs {
91 if bytes.Equal([]byte(epsk.PSKID), pskID.pskID) {
92 psks = append(psks, []byte(epsk.PSK))
93 found = true
94 break
95 }
96 }
97 if !found {
98 t.Fatalf("PSK ID %v not found", pskID.pskID)
99 }
100 }
101
102 keyPairPkg := KeyPairPackage{
103 Public: *keyPkg,
104 Private: PrivateKeyPackage{
105 InitKey: initPriv,
106 EncryptionKey: encryptionPriv,
107 SignatureKey: signaturePriv,
108 },
109 }
110 disableLifetimeCheck := func() time.Time { return time.Time{} }
111 group, err := groupFromSecrets(welcome, &keyPairPkg, groupSecrets, &groupFromSecretsOptions{
112 rawTree: []byte(tc.RatchetTree),
113 psks: psks,
114 now: disableLifetimeCheck,
115 })
116 if err != nil {
117 t.Errorf("groupFromWelcomeAndSecrets() = %v", err)
118 }
119
120 epochAuthenticator, err := cs.deriveSecret(group.epochSecret, secretLabelAuthentication)
121 if err != nil {
122 t.Errorf("deriveSecret(authentication) = %v", err)
123 } else if !bytes.Equal(epochAuthenticator, []byte(tc.InitialEpochAuthenticator)) {
124 t.Errorf("deriveSecret(authentication) = %v, want %v", epochAuthenticator, tc.InitialEpochAuthenticator)
125 }
126
127 for i, epoch := range tc.Epochs {
128 t.Logf("epoch %v", i)
129
130 for i, rawProposal := range epoch.Proposals {
131 if _, _, err := group.UnmarshalAndProcessMessage(rawProposal); err != nil {
132 t.Fatalf("UnmarshalAndProcessMessage(proposal[%v]) = %v", i, err)
133 }
134 }
135
136 var msg mlsMessage
137 if err := unmarshal([]byte(epoch.Commit), &msg); err != nil {
138 t.Fatalf("unmarshal(commit) = %v", err)
139 } else if msg.wireFormat != wireFormatMLSPublicMessage {
140 t.Fatalf("TODO: wireFormat = %v", msg.wireFormat)
141 }
142 pubMsg := msg.publicMessage
143
144 authContent, err := group.verifyPublicMessage(pubMsg)
145 if err != nil {
146 t.Errorf("verifyPublicMessage() = %v", err)
147 }
148 senderLeafIndex := pubMsg.content.sender.leafIndex
149
150 if authContent.content.contentType != contentTypeCommit {
151 t.Errorf("contentType = %v, want %v", authContent.content.contentType, contentTypeCommit)
152 }
153 commit := authContent.content.commit
154
155 proposals, _, err := resolveProposals(commit.proposals, senderLeafIndex, group.pendingProposals)
156 for _, prop := range proposals {
157 switch prop.proposalType {
158 case proposalTypeAdd, proposalTypeRemove, proposalTypeUpdate:
159 // handled by ratchetTree.apply
160 case proposalTypePSK:
161 // handled below
162 default:
163 t.Skipf("TODO: proposal type = %v", prop.proposalType)
164 }
165 }
166
167 var (
168 pskIDs []preSharedKeyID
169 psks [][]byte
170 )
171 for _, prop := range proposals {
172 if prop.proposalType != proposalTypePSK {
173 continue
174 }
175
176 pskID := prop.preSharedKey.psk
177 if pskID.pskType != pskTypeExternal {
178 t.Skipf("TODO: PSK ID type = %v", pskID.pskType)
179 }
180
181 found := false
182 for _, epsk := range tc.ExternalPSKs {
183 if bytes.Equal([]byte(epsk.PSKID), pskID.pskID) {
184 pskIDs = append(pskIDs, pskID)
185 psks = append(psks, []byte(epsk.PSK))
186 found = true
187 break
188 }
189 }
190 if !found {
191 t.Fatalf("PSK ID %v not found", pskID.pskID)
192 }
193 }
194
195 err = group.processCommit(authContent, pskIDs, psks, disableLifetimeCheck)
196 if err != nil {
197 t.Errorf("processCommit() = %v", err)
198 }
199 }
200 }
201
202 func checkEncryptionKeyPair(cs CipherSuite, pub, priv []byte) error {
203 wantPlaintext := []byte("foo")
204 label := []byte("bar")
205
206 kemOutput, ciphertext, err := cs.encryptWithLabel(pub, label, nil, wantPlaintext)
207 if err != nil {
208 return err
209 }
210
211 plaintext, err := cs.decryptWithLabel(priv, label, nil, kemOutput, ciphertext)
212 if err != nil {
213 return err
214 }
215
216 if !bytes.Equal(plaintext, wantPlaintext) {
217 return fmt.Errorf("got plaintext %v, want %v", plaintext, wantPlaintext)
218 }
219
220 return nil
221 }
222
223 func checkSignatureKeyPair(cs CipherSuite, pub, priv []byte) error {
224 content := []byte("foo")
225 label := []byte("bar")
226
227 signature, err := cs.signWithLabel(priv, label, content)
228 if err != nil {
229 return err
230 }
231
232 if !cs.verifyWithLabel(pub, label, content, signature) {
233 return fmt.Errorf("signature verification failed")
234 }
235
236 return nil
237 }
238
239 func TestPassiveClientWelcome(t *testing.T) {
240 var tests []passiveClientTest
241 loadTestVector(t, "testdata/passive-client-welcome.json", &tests)
242
243 for i, tc := range tests {
244 t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) {
245 testPassiveClient(t, &tc)
246 })
247 }
248 }
249
250 func TestPassiveClientCommit(t *testing.T) {
251 var tests []passiveClientTest
252 loadTestVector(t, "testdata/passive-client-handling-commit.json", &tests)
253
254 for i, tc := range tests {
255 t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) {
256 testPassiveClient(t, &tc)
257 })
258 }
259 }
260
261 func TestPassiveClientRandom(t *testing.T) {
262 var tests []passiveClientTest
263 loadTestVector(t, "testdata/passive-client-random.json", &tests)
264
265 for i, tc := range tests {
266 t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) {
267 testPassiveClient(t, &tc)
268 })
269 }
270 }
271