RTCPeerConnection-peerIdentity.https.html (12395B)
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> 6 <script src="identity-helper.sub.js"></script> 7 <script> 8 'use strict'; 9 10 // Test is based on the following editor draft: 11 // https://w3c.github.io/webrtc-identity/identity.html 12 13 // The tests here interacts with the mock identity provider located at 14 // /.well-known/idp-proxy/mock-idp.js 15 16 // The following helper functions are called from identity-helper.sub.js 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 */ 57 promise_test(async t => { 58 const pc1 = new RTCPeerConnection(); 59 t.add_cleanup(() => pc1.close()); 60 const pc2 = new RTCPeerConnection(); 61 62 t.add_cleanup(() => pc2.close()); 63 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 const peerIdentity = pc2.peerIdentity; 74 await pc2.setRemoteDescription(await pc1.createOffer()); 75 const { idp, name } = await peerIdentity; 76 assert_equals(idp, idpHost, `Expect IdP to be ${idpHost}`); 77 assert_equals(name, `alice@${idpDomain}`, 78 `Expect validated identity from mock-idp.js to be same as specified in usernameHint`); 79 }, 'setRemoteDescription() on offer with a=identity should establish peerIdentity'); 80 81 promise_test(async t => { 82 const port = window.location.port; 83 const [idpDomain] = getIdpDomains(); 84 const idpHost = hostString(idpDomain, port); 85 86 const pc1 = new RTCPeerConnection(); 87 t.add_cleanup(() => pc1.close()); 88 pc1.setIdentityProvider(idpHost, { 89 protocol: 'mock-idp.js', 90 usernameHint: `doesnt_matter@${idpDomain}` 91 }); 92 93 const pc2 = new RTCPeerConnection({ 94 peerIdentity: `bob@${idpDomain}` 95 }); 96 97 t.add_cleanup(() => pc2.close()); 98 99 pc2.setIdentityProvider(idpHost, { 100 protocol: 'mock-idp.js', 101 usernameHint: `alice@${idpDomain}` 102 }); 103 104 const offer = await pc1.createOffer(); 105 106 await promise_rejects_dom(t, 'OperationError', 107 pc2.setRemoteDescription(offer)); 108 await promise_rejects_dom(t, 'OperationError', 109 pc2.peerIdentity); 110 }, 'setRemoteDescription() on offer with a=identity that resolve to value different from target peer identity should reject with OperationError'); 111 112 /* 113 9.4. Verifying Identity Assertions 114 8. The RTCPeerConnection decodes the contents and validates that it contains a 115 fingerprint value for every a=fingerprint attribute in the session description. 116 This ensures that the certificate used by the remote peer for communications 117 is covered by the identity assertion. 118 119 If identity validation fails, the peerIdentity promise is rejected with a newly 120 created OperationError. 121 122 If identity validation fails and there is a target peer identity for the 123 RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected 124 with the same DOMException. 125 */ 126 promise_test(t => { 127 const port = window.location.port; 128 const [idpDomain] = getIdpDomains(); 129 const idpHost = hostString(idpDomain, port); 130 131 const pc1 = new RTCPeerConnection(); 132 t.add_cleanup(() => pc1.close()); 133 const pc2 = new RTCPeerConnection({ 134 peerIdentity: `alice@${idpDomain}` 135 }); 136 137 t.add_cleanup(() => pc2.close()); 138 139 // Ask mockidp.js to return custom contents in validation result 140 pc1.setIdentityProvider(idpHost, { 141 protocol: 'mock-idp.js?validatorAction=return-custom-contents&contents=bogus', 142 usernameHint: `alice@${idpDomain}` 143 }); 144 145 const peerIdentityPromise = pc2.peerIdentity; 146 147 return pc1.createOffer() 148 .then(offer => Promise.all([ 149 promise_rejects_dom(t, 'IdpError', 150 pc2.setRemoteDescription(offer)), 151 promise_rejects_dom(t, 'OperationError', 152 peerIdentityPromise) 153 ])); 154 }, 'setRemoteDescription() with peerIdentity set and with IdP proxy that return validationAssertion with mismatch contents should reject with OperationError'); 155 156 /* 157 9.4. Verifying Identity Assertions 158 9. The RTCPeerConnection validates that the domain portion of the identity matches 159 the domain of the IdP as described in [RTCWEB-SECURITY-ARCH]. If this check 160 fails then the identity validation fails. 161 */ 162 promise_test(t => { 163 const port = window.location.port; 164 const [idpDomain1, idpDomain2] = getIdpDomains(); 165 assert_not_equals(idpDomain1, idpDomain2, 166 'Sanity check two idpDomains are different'); 167 168 const idpHost1 = hostString(idpDomain1, port); 169 170 const pc1 = new RTCPeerConnection(); 171 t.add_cleanup(() => pc1.close()); 172 const pc2 = new RTCPeerConnection({ 173 peerIdentity: `alice@${idpDomain2}` 174 }); 175 176 t.add_cleanup(() => pc2.close()); 177 178 // mock-idp.js will return assertion of domain2 identity 179 // with domain1 in the idp.domain field 180 pc1.setIdentityProvider(idpHost1, { 181 protocol: 'mock-idp.js', 182 usernameHint: `alice@${idpDomain2}` 183 }); 184 185 return pc1.getIdentityAssertion() 186 .then(assertionResultStr => { 187 const { idp, assertion } = parseAssertionResult(assertionResultStr); 188 189 assert_equals(idp.domain, idpHost1, 190 'Sanity check domain of assertion is host1'); 191 192 assert_equals(assertion.args.options.usernameHint, `alice@${idpDomain2}`, 193 'Sanity check domain1 is going to validate a domain2 identity'); 194 195 return pc1.createOffer(); 196 }) 197 .then(offer => Promise.all([ 198 promise_rejects_dom(t, 'OperationError', 199 pc2.setRemoteDescription(offer)), 200 promise_rejects_dom(t, 'OperationError', 201 pc2.peerIdentity) 202 ])); 203 }, 'setRemoteDescription() and peerIdentity should reject with OperationError if IdP return validated identity that is different from its own domain'); 204 205 /* 206 9.4 Verifying Identity Assertions 207 If identity validation fails and there is a target peer identity for the 208 RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected 209 with the same DOMException. 210 211 9.5 IdP Error Handling 212 - If an identity provider throws an exception or returns a promise that is ultimately 213 rejected, then the procedure that depends on the IdP MUST also fail. These types of 214 errors will cause an IdP failure with an RTCError with errorDetail set to 215 "idp-execution-failure". 216 217 Any error generated by the IdP MAY provide additional information in the 218 idpErrorInfo attribute. The information in this string is defined by the 219 IdP in use. 220 */ 221 promise_test(t => { 222 const port = window.location.port; 223 const [idpDomain] = getIdpDomains(); 224 const idpHost = hostString(idpDomain, port); 225 226 const pc1 = new RTCPeerConnection(); 227 t.add_cleanup(() => pc1.close()); 228 const pc2 = new RTCPeerConnection({ 229 peerIdentity: `alice@${idpDomain}` 230 }); 231 232 t.add_cleanup(() => pc2.close()); 233 234 // Ask mock-idp.js to throw error during validation, 235 // i.e. during pc2.setRemoteDescription() 236 pc1.setIdentityProvider(idpHost, { 237 protocol: 'mock-idp.js?validatorAction=throw-error&errorInfo=bar', 238 usernameHint: `alice@${idpDomain}` 239 }); 240 241 return pc1.createOffer() 242 .then(offer => Promise.all([ 243 assert_rtcerror_rejection('idp-execution-failure', 244 pc2.setRemoteDescription(offer)), 245 assert_rtcerror_rejection('idp-execution-failure', 246 pc2.peerIdentity) 247 ])) 248 .then(() => { 249 assert_equals(pc2.idpErrorInfo, 'bar', 250 'Expect pc2.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js'); 251 }); 252 }, `When IdP throws error and pc has target peer identity, setRemoteDescription() and peerIdentity rejected with RTCError('idp-execution-error')`); 253 254 /* 255 4.3.2. setRemoteDescription 256 If there is no target peer identity, then setRemoteDescription does not await the 257 completion of identity validation. 258 259 9.5. IdP Error Handling 260 - If an identity provider throws an exception or returns a promise that is 261 ultimately rejected, then the procedure that depends on the IdP MUST also fail. 262 These types of errors will cause an IdP failure with an RTCError with errorDetail 263 set to "idp-execution-failure". 264 265 9.4. Verifying Identity Assertions 266 If identity validation fails and there is no a target peer identity, the value of 267 the peerIdentity MUST be set to a new, unresolved promise instance. This permits 268 the use of renegotiation (or a subsequent answer, if the session description was 269 a provisional answer) to resolve or reject the identity. 270 */ 271 promise_test(t => { 272 const pc1 = new RTCPeerConnection(); 273 t.add_cleanup(() => pc1.close()); 274 const pc2 = new RTCPeerConnection(); 275 276 t.add_cleanup(() => pc2.close()); 277 278 const port = window.location.port; 279 const [idpDomain] = getIdpDomains(); 280 const idpHost = hostString(idpDomain, port); 281 282 // Ask mock-idp.js to throw error during validation, 283 // i.e. during pc2.setRemoteDescription() 284 pc1.setIdentityProvider(idpHost, { 285 protocol: 'mock-idp.js?validatorAction=throw-error', 286 usernameHint: `alice@${idpDomain}` 287 }); 288 289 const peerIdentityPromise1 = pc2.peerIdentity; 290 291 return pc1.createOffer() 292 .then(offer => 293 // setRemoteDescription should succeed because there is no target peer identity set 294 pc2.setRemoteDescription(offer)) 295 .then(() => 296 assert_rtcerror_rejection('idp-execution-failure', 297 peerIdentityPromise1, 298 `Expect first peerIdentity promise to be rejected with RTCError('idp-execution-failure')`)) 299 .then(() => { 300 const peerIdentityPromise2 = pc2.peerIdentity; 301 assert_not_equals(peerIdentityPromise2, peerIdentityPromise1, 302 'Expect pc2.peerIdentity to be replaced with a fresh unresolved promise'); 303 304 // regenerate an identity assertion with no test option to throw error 305 pc1.setIdentityProvider(idpHost, { 306 protocol: 'idp-test.js', 307 usernameHint: `alice@${idpDomain}` 308 }); 309 310 return pc1.createOffer() 311 .then(offer => pc2.setRemoteDescription(offer)) 312 .then(peerIdentityPromise2) 313 .then(identityAssertion => { 314 const { idp, name } = identityAssertion; 315 316 assert_equals(idp, idpDomain, 317 `Expect IdP domain to be ${idpDomain}`); 318 319 assert_equals(name, `alice@${idpDomain}`, 320 `Expect validated identity to be alice@${idpDomain}`); 321 322 assert_equals(pc2.peeridentity, peerIdentityPromise2, 323 'Expect pc2.peerIdentity to stay fixed after identity is validated'); 324 }); 325 }); 326 }, 'IdP failure with no target peer identity should have following setRemoteDescription() succeed and replace pc.peerIdentity with a new promise'); 327 328 </script>