Allow other credentials than just password

Makes the XVP authentication mechanism more general.
This commit is contained in:
Pierre Ossman 2017-10-13 13:50:49 +02:00
parent 233c8b6a53
commit 430f00d6fe
6 changed files with 105 additions and 98 deletions

View File

@ -206,7 +206,7 @@ var UI = {
'onNotification': UI.notification,
'onUpdateState': UI.updateState,
'onDisconnected': UI.disconnectFinished,
'onPasswordRequired': UI.passwordRequired,
'onCredentialsRequired': UI.credentials,
'onXvpInit': UI.updateXvpButton,
'onClipboard': UI.clipboardReceive,
'onBell': UI.bell,
@ -1067,7 +1067,7 @@ var UI = {
UI.updateLocalCursor();
UI.updateViewOnly();
UI.rfb.connect(host, port, password, path);
UI.rfb.connect(host, port, { password: password }, path);
},
disconnect: function() {
@ -1127,8 +1127,8 @@ var UI = {
* PASSWORD
* ------v------*/
passwordRequired: function(rfb, msg) {
credentials: function(rfb, types) {
// FIXME: handle more types
document.getElementById('noVNC_password_dlg')
.classList.add('noVNC_open');
@ -1136,9 +1136,7 @@ var UI = {
document.getElementById('noVNC_password_input').focus();
}, 100);
if (typeof msg === 'undefined') {
msg = _("Password is required");
}
var msg = _("Password is required");
Log.Warn(msg);
UI.showStatus(msg, "warning");
},
@ -1148,7 +1146,7 @@ var UI = {
var password = inputElem.value;
// Clear the input after reading the password
inputElem.value = "";
UI.rfb.sendPassword(password);
UI.rfb.sendCredentials({ password: password });
UI.reconnect_password = password;
document.getElementById('noVNC_password_dlg')
.classList.remove('noVNC_open');

View File

@ -36,7 +36,7 @@ export default function RFB(defaults) {
this._rfb_host = '';
this._rfb_port = 5900;
this._rfb_password = '';
this._rfb_credentials = {};
this._rfb_path = '';
this._rfb_connection_state = '';
@ -124,7 +124,6 @@ export default function RFB(defaults) {
'local_cursor': false, // Request locally rendered cursor
'shared': true, // Request shared mode
'view_only': false, // Disable client mouse/keyboard
'xvp_password_sep': '@', // Separator for XVP password fields
'disconnectTimeout': 3, // Time (s) to wait for disconnection
'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection
'repeaterID': '', // [UltraVNC] RepeaterID to connect to
@ -134,7 +133,7 @@ export default function RFB(defaults) {
'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change
'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI
'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished
'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required
'onCredentialsRequired': function () { }, // onCredentialsRequired(rfb, types): VNC credentials are required
'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
'onBell': function () { }, // onBell(rfb): RFB Bell message received
'onFBUReceive': function () { }, // onFBUReceive(rfb, rect): RFB FBU rect received but not yet processed
@ -241,10 +240,10 @@ export default function RFB(defaults) {
RFB.prototype = {
// Public methods
connect: function (host, port, password, path) {
connect: function (host, port, creds, path) {
this._rfb_host = host;
this._rfb_port = port;
this._rfb_password = (password !== undefined) ? password : "";
this._rfb_credentials = (creds !== undefined) ? creds : {};
this._rfb_path = (path !== undefined) ? path : "";
if (!this._rfb_host) {
@ -264,8 +263,8 @@ RFB.prototype = {
this._sock.off('open');
},
sendPassword: function (passwd) {
this._rfb_password = passwd;
sendCredentials: function (creds) {
this._rfb_credentials = creds;
setTimeout(this._init_msg.bind(this), 0);
},
@ -848,21 +847,18 @@ RFB.prototype = {
// authentication
_negotiate_xvp_auth: function () {
var xvp_sep = this._xvp_password_sep;
var xvp_auth = this._rfb_password.split(xvp_sep);
if (xvp_auth.length < 3) {
var msg = 'XVP credentials required (user' + xvp_sep +
'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
this._onPasswordRequired(this, msg);
if (!this._rfb_credentials.username ||
!this._rfb_credentials.password ||
!this._rfb_credentials.target) {
this._onCredentialsRequired(this, ["username", "password", "target"]);
return false;
}
var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
String.fromCharCode(xvp_auth[1].length) +
xvp_auth[0] +
xvp_auth[1];
var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
String.fromCharCode(this._rfb_credentials.target.length) +
this._rfb_credentials.username +
this._rfb_credentials.target;
this._sock.send_string(xvp_auth_str);
this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
this._rfb_auth_scheme = 2;
return this._negotiate_authentication();
},
@ -870,14 +866,14 @@ RFB.prototype = {
_negotiate_std_vnc_auth: function () {
if (this._sock.rQwait("auth challenge", 16)) { return false; }
if (this._rfb_password.length === 0) {
this._onPasswordRequired(this);
if (!this._rfb_credentials.password) {
this._onCredentialsRequired(this, ["password"]);
return false;
}
// TODO(directxman12): make genDES not require an Array
var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
var response = RFB.genDES(this._rfb_password, challenge);
var response = RFB.genDES(this._rfb_credentials.password, challenge);
this._sock.send(response);
this._rfb_init_state = "SecurityResult";
return true;
@ -1496,7 +1492,6 @@ make_properties(RFB, [
['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
['scale', 'rw', 'float'], // Display area scale factor
['viewport', 'rw', 'bool'], // Use viewport clipping
['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
@ -1506,7 +1501,7 @@ make_properties(RFB, [
['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change
['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI
['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished
['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required
['onCredentialsRequired', 'rw', 'func'], // onCredentialsRequired(rfb, types): VNC credentials are required
['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed

View File

@ -37,7 +37,6 @@ attribute mode is one of the following:
| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
| scale | float | RW | 1.0 | Display area scale factor
| viewport | bool | RW | false | Use viewport clipping
| xvp_password_sep | str | RW | '@' | Separator for XVP password fields
| disconnectTimeout | int | RW | 3 | Time (in seconds) to wait for disconnection
| wsProtocols | arr | RW | ['binary'] | Protocols to use in the WebSocket connection
| repeaterID | str | RW | '' | UltraVNC RepeaterID to connect to
@ -50,22 +49,22 @@ In addition to the getter and setter methods to modify configuration
attributes, the RFB object has other methods that are available in the
object instance.
| name | parameters | description
| ------------------ | ------------------------------ | ------------
| connect | (host, port, password, path) | Connect to the given host:port/path. Optional password and path.
| disconnect | () | Disconnect
| sendPassword | (passwd) | Send password after onPasswordRequired callback
| sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence
| xvpOp | (ver, op) | Send a XVP operation (2=shutdown, 3=reboot, 4=reset)
| xvpShutdown | () | Send XVP shutdown.
| xvpReboot | () | Send XVP reboot.
| xvpReset | () | Send XVP reset.
| sendKey | (keysym, code, down) | Send a key press event. If down not specified, send a down and up event.
| clipboardPasteFrom | (text) | Send a clipboard paste event
| autoscale | (width, height, downscaleOnly) | Scale the display
| clippingDisplay | () | Check if the remote display is larger than the client display
| requestDesktopSize | (width, height) | Send a request to change the remote desktop size.
| viewportChangeSize | (width, height) | Change size of the viewport
| name | parameters | description
| ------------------ | ------------------------------- | ------------
| connect | (host, port, credentials, path) | Connect to the given host:port/path. Optional credentials and path.
| disconnect | () | Disconnect
| sendCredentials | (credentials) | Send credentials after onCredentialsRequired callback
| sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence
| xvpOp | (ver, op) | Send a XVP operation (2=shutdown, 3=reboot, 4=reset)
| xvpShutdown | () | Send XVP shutdown.
| xvpReboot | () | Send XVP reboot.
| xvpReset | () | Send XVP reset.
| sendKey | (keysym, code, down) | Send a key press event. If down not specified, send a down and up event.
| clipboardPasteFrom | (text) | Send a clipboard paste event
| autoscale | (width, height, downscaleOnly) | Scale the display
| clippingDisplay | () | Check if the remote display is larger than the client display
| requestDesktopSize | (width, height) | Send a request to change the remote desktop size.
| viewportChangeSize | (width, height) | Change size of the viewport
## 3 Callbacks
@ -73,19 +72,19 @@ object instance.
The RFB object has certain events that can be hooked with callback
functions.
| name | parameters | description
| ------------------ | -------------------------- | ------------
| onUpdateState | (rfb, state, oldstate) | Connection state change (see details below)
| onNotification | (rfb, msg, level, options) | Notification for the UI (optional options)
| onDisconnected | (rfb, reason) | Disconnection finished with an optional reason. No reason specified means normal disconnect.
| onPasswordRequired | (rfb, msg) | VNC password is required (use sendPassword), optionally comes with a message.
| onClipboard | (rfb, text) | RFB clipboard contents received
| onBell | (rfb) | RFB Bell message received
| onFBUReceive | (rfb, fbu) | RFB FBU received but not yet processed (see details below)
| onFBUComplete | (rfb, fbu) | RFB FBU received and processed (see details below)
| onFBResize | (rfb, width, height) | Frame buffer (remote desktop) size changed
| onDesktopName | (rfb, name) | VNC desktop name recieved
| onXvpInit | (version) | XVP extensions active for this connection.
| name | parameters | description
| --------------------- | -------------------------- | ------------
| onUpdateState | (rfb, state, oldstate) | Connection state change (see details below)
| onNotification | (rfb, msg, level, options) | Notification for the UI (optional options)
| onDisconnected | (rfb, reason) | Disconnection finished with an optional reason. No reason specified means normal disconnect.
| onCredentialsRequired | (rfb, types) | VNC credentials are required (use sendCredentials)
| onClipboard | (rfb, text) | RFB clipboard contents received
| onBell | (rfb) | RFB Bell message received
| onFBUReceive | (rfb, fbu) | RFB FBU received but not yet processed (see details below)
| onFBUComplete | (rfb, fbu) | RFB FBU received and processed (see details below)
| onFBResize | (rfb, width, height) | Frame buffer (remote desktop) size changed
| onDesktopName | (rfb, name) | VNC desktop name recieved
| onXvpInit | (version) | XVP extensions active for this connection.
__RFB onUpdateState callback details__
@ -103,6 +102,20 @@ created for new connections.
| disconnecting | starting to disconnect
| disconnected | disconnected - permanent end-state for this RFB object
__RFB onCredentialsRequired callback details__
The onCredentialsRequired callback is called when the server requests more
credentials than was specified to connect(). The types argument is a list
of all the credentials that are required. Currently the following are
defined:
| name | description
| -------- | ------------
| username | User that authenticates
| password | Password for user
| target | String specifying target machine or session
__RFB onFBUReceive and on FBUComplete callback details__
The onFBUReceive callback is invoked when a frame buffer update

View File

@ -104,10 +104,10 @@ RecordingPlayer.prototype = {
this._rfb._sock.close = function () {};
this._rfb._sock.flush = function () {};
this._rfb._checkEvents = function () {};
this._rfb.connect = function (host, port, password, path) {
this._rfb.connect = function (host, port, credentials, path) {
this._rfb_host = host;
this._rfb_port = port;
this._rfb_password = (password !== undefined) ? password : "";
this._rfb_credentials = {};
this._rfb_path = (path !== undefined) ? path : "";
this._sock.init('binary', 'ws');
this._rfb_connection_state = 'connecting';

View File

@ -116,18 +116,18 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
});
describe('#sendPassword', function () {
describe('#sendCredentials', function () {
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
afterEach(function () { this.clock.restore(); });
it('should set the rfb password properly"', function () {
client.sendPassword('pass');
expect(client._rfb_password).to.equal('pass');
it('should set the rfb credentials properly"', function () {
client.sendCredentials({ password: 'pass' });
expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
});
it('should call init_msg "soon"', function () {
client._init_msg = sinon.spy();
client.sendPassword('pass');
client.sendCredentials({ password: 'pass' });
this.clock.tick(5);
expect(client._init_msg).to.have.been.calledOnce;
});
@ -836,21 +836,22 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._rfb_version = 3.8;
});
it('should call the passwordRequired callback if missing a password', function () {
client.set_onPasswordRequired(sinon.spy());
it('should call the onCredentialsRequired callback if missing a password', function () {
client.set_onCredentialsRequired(sinon.spy());
send_security(2, client);
var challenge = [];
for (var i = 0; i < 16; i++) { challenge[i] = i; }
client._sock._websocket._receive_data(new Uint8Array(challenge));
var spy = client.get_onPasswordRequired();
expect(client._rfb_password.length).to.equal(0);
var spy = client.get_onCredentialsRequired();
expect(client._rfb_credentials).to.be.empty;
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][1]).to.have.members(["password"]);
});
it('should encrypt the password with DES and then send it back', function () {
client._rfb_password = 'passwd';
client._rfb_credentials = { password: 'passwd' };
send_security(2, client);
client._sock._websocket._get_sent_data(); // skip the choice of auth reply
@ -863,7 +864,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should transition to SecurityResult immediately after sending the password', function () {
client._rfb_password = 'passwd';
client._rfb_credentials = { password: 'passwd' };
send_security(2, client);
var challenge = [];
@ -886,41 +887,44 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fall through to standard VNC authentication upon completion', function () {
client.set_xvp_password_sep('#');
client._rfb_password = 'user#target#password';
client._rfb_credentials = { username: 'user',
target: 'target',
password: 'password' };
client._negotiate_std_vnc_auth = sinon.spy();
send_security(22, client);
expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
});
it('should call the passwordRequired callback if the password is missing', function() {
client.set_onPasswordRequired(sinon.spy());
client._rfb_password = '';
it('should call the onCredentialsRequired callback if all credentials are missing', function() {
client.set_onCredentialsRequired(sinon.spy());
client._rfb_credentials = {};
send_security(22, client);
var spy = client.get_onPasswordRequired();
expect(client._rfb_password.length).to.equal(0);
var spy = client.get_onCredentialsRequired();
expect(client._rfb_credentials).to.be.empty;
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][1]).to.have.members(["username", "password", "target"]);
});
it('should call the passwordRequired callback if the password is improperly formatted', function() {
client.set_onPasswordRequired(sinon.spy());
client._rfb_password = 'user@target';
it('should call the onCredentialsRequired callback if some credentials are missing', function() {
client.set_onCredentialsRequired(sinon.spy());
client._rfb_credentials = { username: 'user',
target: 'target' };
send_security(22, client);
var spy = client.get_onPasswordRequired();
var spy = client.get_onCredentialsRequired();
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][1]).to.have.members(["username", "password", "target"]);
});
it('should split the password, send the first two parts, and pass on the last part', function () {
client.set_xvp_password_sep('#');
client._rfb_password = 'user#target#password';
it('should send user and target separately', function () {
client._rfb_credentials = { username: 'user',
target: 'target',
password: 'password' };
client._negotiate_std_vnc_auth = sinon.spy();
send_security(22, client);
expect(client._rfb_password).to.equal('password');
var expected = [22, 4, 6]; // auth selection, len user, len target
for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }

View File

@ -99,10 +99,7 @@
function updateDesktopName(rfb, name) {
desktopName = name;
}
function passwordRequired(rfb, msg) {
if (typeof msg === 'undefined') {
msg = 'Password Required: ';
}
function credentials(rfb, types) {
var html;
var form = document.createElement('form');
@ -115,10 +112,10 @@
document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
document.getElementById('noVNC_status').innerHTML = '';
document.getElementById('noVNC_status').appendChild(form);
document.getElementById('noVNC_status').querySelector('label').textContent = msg;
document.getElementById('noVNC_status').querySelector('label').textContent = 'Password Required: ';
}
function setPassword() {
rfb.sendPassword(document.getElementById('password_input').value);
rfb.sendCredentials({ password: document.getElementById('password_input').value });
return false;
}
function sendCtrlAltDel() {
@ -266,7 +263,7 @@
'onUpdateState': updateState,
'onDisconnected': disconnected,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired,
'onCredentialsRequired': credentials,
'onFBUComplete': FBUComplete,
'onDesktopName': updateDesktopName});
} catch (exc) {
@ -274,7 +271,7 @@
return; // don't continue trying to connect
}
rfb.connect(host, port, password, path);
rfb.connect(host, port, { password: password }, path);
})();
</script>
</head>