| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 1 | <!doctype html> |
| 2 | <meta charset=utf-8> |
| 3 | <title>RTCPeerConnection.prototype.peerIdentity</title> |
| 4 | <script src="/resources/testharness.js"></script> |
| 5 | <script src="/resources/testharnessreport.js"></script> |
| Mike Pennisi | ad2e6b7 | 2018-05-07 22:17:43 | [diff] [blame] | 6 | <script src="identity-helper.sub.js"></script> |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 7 | <script> |
| 8 | 'use strict'; |
| 9 | |
| 10 | // Test is based on the following editor draft: |
| 11 | // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html |
| 12 | |
| 13 | // The tests here interacts with the mock identity provider located at |
| 14 | // /.well-known/idp-proxy/mock-idp.js |
| 15 | |
| Mike Pennisi | ad2e6b7 | 2018-05-07 22:17:43 | [diff] [blame] | 16 | // The following helper functions are called from identity-helper.sub.js |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 17 | // parseAssertionResult |
| 18 | // getIdpDomains |
| 19 | // assert_rtcerror_rejection |
| 20 | // hostString |
| 21 | |
| 22 | /* |
| 23 | 9.6. RTCPeerConnection Interface Extensions |
| 24 | partial interface RTCPeerConnection { |
| 25 | void setIdentityProvider(DOMString provider, |
| 26 | optional RTCIdentityProviderOptions options); |
| 27 | Promise<DOMString> getIdentityAssertion(); |
| 28 | readonly attribute Promise<RTCIdentityAssertion> peerIdentity; |
| 29 | readonly attribute DOMString? idpLoginUrl; |
| 30 | readonly attribute DOMString? idpErrorInfo; |
| 31 | }; |
| 32 | |
| 33 | dictionary RTCIdentityProviderOptions { |
| 34 | DOMString protocol = "default"; |
| 35 | DOMString usernameHint; |
| 36 | DOMString peerIdentity; |
| 37 | }; |
| 38 | |
| 39 | [Constructor(DOMString idp, DOMString name)] |
| 40 | interface RTCIdentityAssertion { |
| 41 | attribute DOMString idp; |
| 42 | attribute DOMString name; |
| 43 | }; |
| 44 | */ |
| 45 | |
| 46 | /* |
| 47 | 4.3.2. setRemoteDescription |
| 48 | If an a=identity attribute is present in the session description, the browser |
| 49 | validates the identity assertion.. |
| 50 | |
| 51 | If the "peerIdentity" configuration is applied to the RTCPeerConnection, this |
| 52 | establishes a target peer identity of the provided value. Alternatively, if the |
| 53 | RTCPeerConnection has previously authenticated the identity of the peer (that |
| 54 | is, there is a current value for peerIdentity ), then this also establishes a |
| 55 | target peer identity. |
| 56 | */ |
| Philipp Hancke | 45f9422 | 2018-05-08 14:58:33 | [diff] [blame] | 57 | promise_test(t => { |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 58 | const pc1 = new RTCPeerConnection(); |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 59 | t.add_cleanup(() => pc1.close()); |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 60 | const pc2 = new RTCPeerConnection(); |
| 61 | |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 62 | t.add_cleanup(() => pc2.close()); |
| 63 | |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 64 | const port = window.location.port; |
| 65 | const [idpDomain] = getIdpDomains(); |
| 66 | const idpHost = hostString(idpDomain, port); |
| 67 | |
| 68 | pc1.setIdentityProvider(idpHost, { |
| 69 | protocol: 'mock-idp.js', |
| 70 | usernameHint: `alice@${idpDomain}` |
| 71 | }); |
| 72 | |
| 73 | return pc1.createOffer() |
| 74 | .then(offer => pc2.setRemoteDescription(offer)) |
| 75 | .then(() => pc2.peerIdentity) |
| 76 | .then(identityAssertion => { |
| 77 | const { idp, name } = identityAssertion; |
| 78 | assert_equals(idp, idpDomain, `Expect IdP domain to be ${idpDomain}`); |
| 79 | assert_equals(identityAssertion, `alice@${idpDomain}`, |
| 80 | `Expect validated identity from mock-idp.js to be same as specified in usernameHint`); |
| 81 | }); |
| 82 | }, 'setRemoteDescription() on offer with a=identity should establish peerIdentity'); |
| 83 | |
| 84 | /* |
| 85 | 4.3.2. setRemoteDescription |
| 86 | The target peer identity cannot be changed once set. Once set, if a different |
| 87 | value is provided, the user agent MUST reject the returned promise with a newly |
| 88 | created InvalidModificationError and abort this operation. The RTCPeerConnection |
| 89 | MUST be closed if the validated peer identity does not match the target peer |
| 90 | identity. |
| 91 | */ |
| 92 | promise_test(t => { |
| 93 | const port = window.location.port; |
| 94 | const [idpDomain] = getIdpDomains(); |
| 95 | const idpHost = hostString(idpDomain, port); |
| 96 | |
| 97 | const pc1 = new RTCPeerConnection(); |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 98 | t.add_cleanup(() => pc1.close()); |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 99 | const pc2 = new RTCPeerConnection({ |
| 100 | peerIdentity: `bob@${idpDomain}` |
| 101 | }); |
| 102 | |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 103 | t.add_cleanup(() => pc2.close()); |
| 104 | |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 105 | pc1.setIdentityProvider(idpHost, { |
| 106 | protocol: 'mock-idp.js', |
| 107 | usernameHint: `alice@${idpDomain}` |
| 108 | }); |
| 109 | |
| 110 | return pc1.createOffer() |
| 111 | .then(offer => |
| 112 | promise_rejects(t, 'InvalidModificationError', |
| 113 | pc2.setRemoteDescription(offer))) |
| 114 | .then(() => { |
| 115 | assert_true(pc2.signalingState, 'closed', |
| 116 | 'Expect peer connection to be closed after mismatch peer identity'); |
| 117 | }); |
| 118 | }, 'setRemoteDescription() on offer with a=identity that resolve to value different from target peer identity should reject with InvalidModificationError'); |
| 119 | |
| 120 | /* |
| 121 | 9.4. Verifying Identity Assertions |
| 122 | 8. The RTCPeerConnection decodes the contents and validates that it contains a |
| 123 | fingerprint value for every a=fingerprint attribute in the session description. |
| 124 | This ensures that the certificate used by the remote peer for communications |
| 125 | is covered by the identity assertion. |
| 126 | |
| 127 | If identity validation fails, the peerIdentity promise is rejected with a newly |
| 128 | created OperationError. |
| 129 | |
| 130 | If identity validation fails and there is a target peer identity for the |
| 131 | RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected |
| 132 | with the same DOMException. |
| 133 | */ |
| 134 | promise_test(t => { |
| 135 | const port = window.location.port; |
| 136 | const [idpDomain] = getIdpDomains(); |
| 137 | const idpHost = hostString(idpDomain, port); |
| 138 | |
| 139 | const pc1 = new RTCPeerConnection(); |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 140 | t.add_cleanup(() => pc1.close()); |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 141 | const pc2 = new RTCPeerConnection({ |
| 142 | peerIdentity: `alice@${idpDomain}` |
| 143 | }); |
| 144 | |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 145 | t.add_cleanup(() => pc2.close()); |
| 146 | |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 147 | // Ask mockidp.js to return custom contents in validation result |
| 148 | pc1.setIdentityProvider(idpHost, { |
| 149 | protocol: 'mock-idp.js?validatorAction=return-custom-contents&contents=bogus', |
| 150 | usernameHint: `alice@${idpDomain}` |
| 151 | }); |
| 152 | |
| 153 | const peerIdentityPromise = pc2.peerIdentity; |
| 154 | |
| 155 | return pc1.createOffer() |
| 156 | .then(offer => Promise.all([ |
| 157 | promise_rejects(t, 'OperationError', |
| 158 | pc2.setRemoteDescription(offer)), |
| 159 | promise_rejects(t, 'OperationError', |
| 160 | peerIdentityPromise) |
| 161 | ])); |
| 162 | }, 'setRemoteDescription() with peerIdentity set and with IdP proxy that return validationAssertion with mismatch contents should reject with OperationError'); |
| 163 | |
| 164 | /* |
| 165 | 9.4. Verifying Identity Assertions |
| 166 | 9. The RTCPeerConnection validates that the domain portion of the identity matches |
| 167 | the domain of the IdP as described in [RTCWEB-SECURITY-ARCH]. If this check |
| 168 | fails then the identity validation fails. |
| 169 | */ |
| 170 | promise_test(t => { |
| 171 | const port = window.location.port; |
| 172 | const [idpDomain1, idpDomain2] = getIdpDomains(); |
| 173 | assert_not_equals(idpDomain1, idpDomain2, |
| 174 | 'Sanity check two idpDomains are different'); |
| 175 | |
| 176 | const idpHost1 = hostString(idpDomain1, port); |
| 177 | |
| 178 | const pc1 = new RTCPeerConnection(); |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 179 | t.add_cleanup(() => pc1.close()); |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 180 | const pc2 = new RTCPeerConnection({ |
| 181 | peerIdentity: `alice@${idpDomain2}` |
| 182 | }); |
| 183 | |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 184 | t.add_cleanup(() => pc2.close()); |
| 185 | |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 186 | // mock-idp.js will return assertion of domain2 identity |
| 187 | // with domain1 in the idp.domain field |
| 188 | pc1.setIdentityProvider(idpHost1, { |
| 189 | protocol: 'mock-idp.js', |
| 190 | usernameHint: `alice@${idpDomain2}` |
| 191 | }); |
| 192 | |
| 193 | return pc1.getIdentityAssertion() |
| 194 | .then(assertionResultStr => { |
| 195 | const { idp, assertion } = parseAssertionResult(assertionResultStr); |
| 196 | |
| 197 | assert_equals(idp.domain, idpDomain1, |
| 198 | 'Sanity check domain of assertion is domain1'); |
| 199 | |
| 200 | assert_equals(assertion.options.usernameHint, `alice@${idpDomain2}`, |
| 201 | 'Sanity check domain1 is going to validate a domain2 identity'); |
| 202 | |
| 203 | return pc1.createOffer(); |
| 204 | }) |
| 205 | .then(offer => Promise.all([ |
| 206 | promise_rejects(t, 'OperationError', |
| 207 | pc2.setRemoteDescription(offer)), |
| 208 | promise_rejects(t, 'OperationError', |
| 209 | pc2.peerIdentity) |
| 210 | ])); |
| 211 | }, 'setRemoteDescription() and peerIdentity should reject with OperationError if IdP return validated identity that is different from its own domain'); |
| 212 | |
| 213 | /* |
| 214 | 9.4 Verifying Identity Assertions |
| 215 | If identity validation fails and there is a target peer identity for the |
| 216 | RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected |
| 217 | with the same DOMException. |
| 218 | |
| 219 | 9.5 IdP Error Handling |
| 220 | - If an identity provider throws an exception or returns a promise that is ultimately |
| 221 | rejected, then the procedure that depends on the IdP MUST also fail. These types of |
| 222 | errors will cause an IdP failure with an RTCError with errorDetail set to |
| 223 | "idp-execution-failure". |
| 224 | |
| 225 | Any error generated by the IdP MAY provide additional information in the |
| 226 | idpErrorInfo attribute. The information in this string is defined by the |
| 227 | IdP in use. |
| 228 | */ |
| 229 | promise_test(t => { |
| 230 | const port = window.location.port; |
| 231 | const [idpDomain] = getIdpDomains(); |
| 232 | const idpHost = hostString(idpDomain, port); |
| 233 | |
| 234 | const pc1 = new RTCPeerConnection(); |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 235 | t.add_cleanup(() => pc1.close()); |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 236 | const pc2 = new RTCPeerConnection({ |
| 237 | peerIdentity: `alice@${idpDomain}` |
| 238 | }); |
| 239 | |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 240 | t.add_cleanup(() => pc2.close()); |
| 241 | |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 242 | // Ask mock-idp.js to throw error during validation, |
| 243 | // i.e. during pc2.setRemoteDescription() |
| 244 | pc1.setIdentityProvider(idpHost, { |
| 245 | protocol: 'mock-idp.js?validatorAction=throw-error&errorInfo=bar', |
| 246 | usernameHint: `alice@${idpDomain}` |
| 247 | }); |
| 248 | |
| 249 | return pc1.createOffer() |
| 250 | .then(offer => Promise.all([ |
| 251 | assert_rtcerror_rejection('idp-execution-failure', |
| 252 | pc2.setRemoteDescription(offer)), |
| 253 | assert_rtcerror_rejection('idp-execution-failure', |
| 254 | pc2.peerIdentity) |
| 255 | ])) |
| 256 | .then(() => { |
| 257 | assert_equals(pc2.idpErrorInfo, 'bar', |
| 258 | 'Expect pc2.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js'); |
| 259 | }); |
| 260 | }, `When IdP throws error and pc has target peer identity, setRemoteDescription() and peerIdentity rejected with RTCError('idp-execution-error')`); |
| 261 | |
| 262 | /* |
| 263 | 4.3.2. setRemoteDescription |
| 264 | If there is no target peer identity, then setRemoteDescription does not await the |
| 265 | completion of identity validation. |
| 266 | |
| 267 | 9.5. IdP Error Handling |
| 268 | - If an identity provider throws an exception or returns a promise that is |
| 269 | ultimately rejected, then the procedure that depends on the IdP MUST also fail. |
| 270 | These types of errors will cause an IdP failure with an RTCError with errorDetail |
| 271 | set to "idp-execution-failure". |
| 272 | |
| 273 | 9.4. Verifying Identity Assertions |
| 274 | If identity validation fails and there is no a target peer identity, the value of |
| 275 | the peerIdentity MUST be set to a new, unresolved promise instance. This permits |
| 276 | the use of renegotiation (or a subsequent answer, if the session description was |
| 277 | a provisional answer) to resolve or reject the identity. |
| 278 | */ |
| 279 | promise_test(t => { |
| 280 | const pc1 = new RTCPeerConnection(); |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 281 | t.add_cleanup(() => pc1.close()); |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 282 | const pc2 = new RTCPeerConnection(); |
| 283 | |
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame] | 284 | t.add_cleanup(() => pc2.close()); |
| 285 | |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 286 | const port = window.location.port; |
| 287 | const [idpDomain] = getIdpDomains(); |
| 288 | const idpHost = hostString(idpDomain, port); |
| 289 | |
| 290 | // Ask mock-idp.js to throw error during validation, |
| 291 | // i.e. during pc2.setRemoteDescription() |
| 292 | pc1.setIdentityProvider(idpHost, { |
| 293 | protocol: 'mock-idp.js?validatorAction=throw-error', |
| 294 | usernameHint: `alice@${idpDomain}` |
| 295 | }); |
| 296 | |
| 297 | const peerIdentityPromise1 = pc2.peerIdentity; |
| 298 | |
| 299 | return pc1.createOffer() |
| 300 | .then(offer => |
| 301 | // setRemoteDescription should succeed because there is no target peer identity set |
| 302 | pc2.setRemoteDescription(offer)) |
| 303 | .then(() => |
| 304 | assert_rtcerror_rejection('idp-execution-failure', |
| Soares Chen | d97f6c2 | 2017-11-20 14:20:48 | [diff] [blame] | 305 | peerIdentityPromise1, |
| Soares Chen | e5a6567 | 2017-07-21 10:08:30 | [diff] [blame] | 306 | `Expect first peerIdentity promise to be rejected with RTCError('idp-execution-failure')`)) |
| 307 | .then(() => { |
| 308 | const peerIdentityPromise2 = pc2.peerIdentity; |
| 309 | assert_not_equals(peerIdentityPromise2, peerIdentityPromise1, |
| 310 | 'Expect pc2.peerIdentity to be replaced with a fresh unresolved promise'); |
| 311 | |
| 312 | // regenerate an identity assertion with no test option to throw error |
| 313 | pc1.setIdentityProvider(idpHost, { |
| 314 | protocol: 'idp-test.js', |
| 315 | usernameHint: `alice@${idpDomain}` |
| 316 | }); |
| 317 | |
| 318 | return pc1.createOffer() |
| 319 | .then(offer => pc2.setRemoteDescription(offer)) |
| 320 | .then(peerIdentityPromise2) |
| 321 | .then(identityAssertion => { |
| 322 | const { idp, name } = identityAssertion; |
| 323 | |
| 324 | assert_equals(idp, idpDomain, |
| 325 | `Expect IdP domain to be ${idpDomain}`); |
| 326 | |
| 327 | assert_equals(name, `alice@${idpDomain}`, |
| 328 | `Expect validated identity to be alice@${idpDomain}`); |
| 329 | |
| 330 | assert_equals(pc2.peeridentity, peerIdentityPromise2, |
| 331 | 'Expect pc2.peerIdentity to stay fixed after identity is validated'); |
| 332 | }); |
| 333 | }); |
| 334 | }, 'IdP failure with no target peer identity should have following setRemoteDescription() succeed and replace pc.peerIdentity with a new promise'); |
| 335 | |
| 336 | </script> |