summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-identity/RTCPeerConnection-peerIdentity.https.html
blob: 268e40621149e0d3aa00613f7f42e2ca6b5efdb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
<!doctype html>
<meta charset=utf-8>
<title>RTCPeerConnection.prototype.peerIdentity</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="identity-helper.sub.js"></script>
<script>
  'use strict';

  // Test is based on the following editor draft:
  //   https://w3c.github.io/webrtc-identity/identity.html

  // The tests here interacts with the mock identity provider located at
  //   /.well-known/idp-proxy/mock-idp.js

  // The following helper functions are called from identity-helper.sub.js
  //   parseAssertionResult
  //   getIdpDomains
  //   assert_rtcerror_rejection
  //   hostString

  /*
    9.6.  RTCPeerConnection Interface Extensions
      partial interface RTCPeerConnection {
        void               setIdentityProvider(DOMString provider,
                                               optional RTCIdentityProviderOptions options);
        Promise<DOMString> getIdentityAssertion();
        readonly attribute Promise<RTCIdentityAssertion> peerIdentity;
        readonly attribute DOMString?                    idpLoginUrl;
        readonly attribute DOMString?                    idpErrorInfo;
      };

      dictionary RTCIdentityProviderOptions {
        DOMString protocol = "default";
        DOMString usernameHint;
        DOMString peerIdentity;
      };

      [Constructor(DOMString idp, DOMString name)]
      interface RTCIdentityAssertion {
        attribute DOMString idp;
        attribute DOMString name;
      };
   */

  /*
    4.3.2. setRemoteDescription
      If an a=identity attribute is present in the session description, the browser
      validates the identity assertion..

      If the "peerIdentity" configuration is applied to the RTCPeerConnection, this
      establishes a target peer identity of the provided value. Alternatively, if the
      RTCPeerConnection has previously authenticated the identity of the peer (that
      is, there is a current value for peerIdentity ), then this also establishes a
      target peer identity.
   */
  promise_test(async t => {
    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    const pc2 = new RTCPeerConnection();

    t.add_cleanup(() => pc2.close());

    const port = window.location.port;
    const [idpDomain] = getIdpDomains();
    const idpHost = hostString(idpDomain, port);

    pc1.setIdentityProvider(idpHost, {
      protocol: 'mock-idp.js',
      usernameHint: `alice@${idpDomain}`
    });

    const peerIdentity = pc2.peerIdentity;
    await pc2.setRemoteDescription(await pc1.createOffer());
    const { idp, name } = await peerIdentity;
    assert_equals(idp, idpHost, `Expect IdP to be ${idpHost}`);
    assert_equals(name, `alice@${idpDomain}`,
      `Expect validated identity from mock-idp.js to be same as specified in usernameHint`);
  }, 'setRemoteDescription() on offer with a=identity should establish peerIdentity');

  promise_test(async t => {
    const port = window.location.port;
    const [idpDomain] = getIdpDomains();
    const idpHost = hostString(idpDomain, port);

    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    pc1.setIdentityProvider(idpHost, {
      protocol: 'mock-idp.js',
      usernameHint: `doesnt_matter@${idpDomain}`
    });

    const pc2 = new RTCPeerConnection({
      peerIdentity: `bob@${idpDomain}`
    });

    t.add_cleanup(() => pc2.close());

    pc2.setIdentityProvider(idpHost, {
      protocol: 'mock-idp.js',
      usernameHint: `alice@${idpDomain}`
    });

    const offer = await pc1.createOffer();

    await promise_rejects_dom(t, 'OperationError',
      pc2.setRemoteDescription(offer));
    await promise_rejects_dom(t, 'OperationError',
      pc2.peerIdentity);
  }, 'setRemoteDescription() on offer with a=identity that resolve to value different from target peer identity should reject with OperationError');

  /*
    9.4.  Verifying Identity Assertions
      8.  The RTCPeerConnection decodes the contents and validates that it contains a
          fingerprint value for every a=fingerprint attribute in the session description.
          This ensures that the certificate used by the remote peer for communications
          is covered by the identity assertion.

      If identity validation fails, the peerIdentity promise is rejected with a newly
      created OperationError.

      If identity validation fails and there is a target peer identity for the
      RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected
      with the same DOMException.
   */
  promise_test(t => {
    const port = window.location.port;
    const [idpDomain] = getIdpDomains();
    const idpHost = hostString(idpDomain, port);

    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    const pc2 = new RTCPeerConnection({
      peerIdentity: `alice@${idpDomain}`
    });

    t.add_cleanup(() => pc2.close());

    // Ask mockidp.js to return custom contents in validation result
    pc1.setIdentityProvider(idpHost, {
      protocol: 'mock-idp.js?validatorAction=return-custom-contents&contents=bogus',
      usernameHint: `alice@${idpDomain}`
    });

    const peerIdentityPromise = pc2.peerIdentity;

    return pc1.createOffer()
    .then(offer => Promise.all([
      promise_rejects_dom(t, 'IdpError',
        pc2.setRemoteDescription(offer)),
      promise_rejects_dom(t, 'OperationError',
        peerIdentityPromise)
    ]));
  }, 'setRemoteDescription() with peerIdentity set and with IdP proxy that return validationAssertion with mismatch contents should reject with OperationError');

  /*
    9.4.  Verifying Identity Assertions
      9.  The RTCPeerConnection validates that the domain portion of the identity matches
          the domain of the IdP as described in [RTCWEB-SECURITY-ARCH]. If this check
          fails then the identity validation fails.
   */
  promise_test(t => {
    const port = window.location.port;
    const [idpDomain1, idpDomain2] = getIdpDomains();
    assert_not_equals(idpDomain1, idpDomain2,
      'Sanity check two idpDomains are different');

    const idpHost1 = hostString(idpDomain1, port);

    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    const pc2 = new RTCPeerConnection({
      peerIdentity: `alice@${idpDomain2}`
    });

    t.add_cleanup(() => pc2.close());

    // mock-idp.js will return assertion of domain2 identity
    // with domain1 in the idp.domain field
    pc1.setIdentityProvider(idpHost1, {
      protocol: 'mock-idp.js',
      usernameHint: `alice@${idpDomain2}`
    });

    return pc1.getIdentityAssertion()
    .then(assertionResultStr => {
      const { idp, assertion } = parseAssertionResult(assertionResultStr);

      assert_equals(idp.domain, idpHost1,
        'Sanity check domain of assertion is host1');

      assert_equals(assertion.args.options.usernameHint, `alice@${idpDomain2}`,
        'Sanity check domain1 is going to validate a domain2 identity');

      return pc1.createOffer();
    })
    .then(offer => Promise.all([
      promise_rejects_dom(t, 'OperationError',
        pc2.setRemoteDescription(offer)),
      promise_rejects_dom(t, 'OperationError',
        pc2.peerIdentity)
    ]));
  }, 'setRemoteDescription() and peerIdentity should reject with OperationError if IdP return validated identity that is different from its own domain');

  /*
    9.4 Verifying Identity Assertions
      If identity validation fails and there is a target peer identity for the
      RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected
      with the same DOMException.

    9.5 IdP Error Handling
      - If an identity provider throws an exception or returns a promise that is ultimately
        rejected, then the procedure that depends on the IdP MUST also fail. These types of
        errors will cause an IdP failure with an RTCError with errorDetail set to
        "idp-execution-failure".

      Any error generated by the IdP MAY provide additional information in the
      idpErrorInfo attribute. The information in this string is defined by the
      IdP in use.
   */
  promise_test(t => {
    const port = window.location.port;
    const [idpDomain] = getIdpDomains();
    const idpHost = hostString(idpDomain, port);

    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    const pc2 = new RTCPeerConnection({
      peerIdentity: `alice@${idpDomain}`
    });

    t.add_cleanup(() => pc2.close());

    // Ask mock-idp.js to throw error during validation,
    // i.e. during pc2.setRemoteDescription()
    pc1.setIdentityProvider(idpHost, {
      protocol: 'mock-idp.js?validatorAction=throw-error&errorInfo=bar',
      usernameHint: `alice@${idpDomain}`
    });

    return pc1.createOffer()
    .then(offer => Promise.all([
      assert_rtcerror_rejection('idp-execution-failure',
        pc2.setRemoteDescription(offer)),
      assert_rtcerror_rejection('idp-execution-failure',
        pc2.peerIdentity)
    ]))
    .then(() => {
      assert_equals(pc2.idpErrorInfo, 'bar',
        'Expect pc2.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js');
    });
  }, `When IdP throws error and pc has target peer identity, setRemoteDescription() and peerIdentity rejected with RTCError('idp-execution-error')`);

  /*
    4.3.2. setRemoteDescription
      If there is no target peer identity, then setRemoteDescription does not await the
      completion of identity validation.

    9.5.  IdP Error Handling
      - If an identity provider throws an exception or returns a promise that is
        ultimately rejected, then the procedure that depends on the IdP MUST also fail.
        These types of errors will cause an IdP failure with an RTCError with errorDetail
        set to "idp-execution-failure".

    9.4.  Verifying Identity Assertions
      If identity validation fails and there is no a target peer identity, the value of
      the peerIdentity MUST be set to a new, unresolved promise instance. This permits
      the use of renegotiation (or a subsequent answer, if the session description was
      a provisional answer) to resolve or reject the identity.
   */
  promise_test(t => {
    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    const pc2 = new RTCPeerConnection();

    t.add_cleanup(() => pc2.close());

    const port = window.location.port;
    const [idpDomain] = getIdpDomains();
    const idpHost = hostString(idpDomain, port);

    // Ask mock-idp.js to throw error during validation,
    // i.e. during pc2.setRemoteDescription()
    pc1.setIdentityProvider(idpHost, {
      protocol: 'mock-idp.js?validatorAction=throw-error',
      usernameHint: `alice@${idpDomain}`
    });

    const peerIdentityPromise1 = pc2.peerIdentity;

    return pc1.createOffer()
    .then(offer =>
      // setRemoteDescription should succeed because there is no target peer identity set
      pc2.setRemoteDescription(offer))
    .then(() =>
      assert_rtcerror_rejection('idp-execution-failure',
        peerIdentityPromise1,
        `Expect first peerIdentity promise to be rejected with RTCError('idp-execution-failure')`))
    .then(() => {
      const peerIdentityPromise2 = pc2.peerIdentity;
      assert_not_equals(peerIdentityPromise2, peerIdentityPromise1,
        'Expect pc2.peerIdentity to be replaced with a fresh unresolved promise');

      // regenerate an identity assertion with no test option to throw error
      pc1.setIdentityProvider(idpHost, {
        protocol: 'idp-test.js',
        usernameHint: `alice@${idpDomain}`
      });

      return pc1.createOffer()
      .then(offer => pc2.setRemoteDescription(offer))
      .then(peerIdentityPromise2)
      .then(identityAssertion => {
        const { idp, name } = identityAssertion;

        assert_equals(idp, idpDomain,
          `Expect IdP domain to be ${idpDomain}`);

        assert_equals(name, `alice@${idpDomain}`,
          `Expect validated identity to be alice@${idpDomain}`);

        assert_equals(pc2.peeridentity, peerIdentityPromise2,
          'Expect pc2.peerIdentity to stay fixed after identity is validated');
      });
    });
  }, 'IdP failure with no target peer identity should have following setRemoteDescription() succeed and replace pc.peerIdentity with a new promise');

</script>