328 lines
12 KiB
HTML
328 lines
12 KiB
HTML
<!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>
|