feat: support uploading of ssh-rsa private key from client for authentication #381

This commit is contained in:
Bill Church 2024-12-02 02:19:16 +00:00
parent c48fadba08
commit 2f4083ff4f
No known key found for this signature in database
3 changed files with 32 additions and 45 deletions

View file

@ -25,6 +25,8 @@ class WebSSH2Socket extends EventEmitter {
authenticated: false,
username: null,
password: null,
privatekey: null,
keyPassword: null,
host: null,
port: null,
term: null,
@ -110,9 +112,16 @@ class WebSSH2Socket extends EventEmitter {
debug(`handleAuthenticate: ${this.socket.id}, %O`, maskSensitiveData(creds))
if (isValidCredentials(creds)) {
// Set term if provided, otherwise use config default
this.sessionState.term = validateSshTerm(creds.term)
? creds.term
: this.config.ssh.term
// Map the client's privateKey field to our internal privatekey field if present
if (creds.privateKey) {
creds.privatekey = creds.privateKey
}
this.initializeConnection(creds)
} else {
debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
@ -129,9 +138,8 @@ class WebSSH2Socket extends EventEmitter {
maskSensitiveData(creds)
)
// Add private key from config if available
// Add private key from config if available and not provided in creds
if (this.config.user.privatekey && !creds.privatekey) {
// eslint-disable-next-line no-param-reassign
creds.privatekey = this.config.user.privatekey
}
@ -143,6 +151,7 @@ class WebSSH2Socket extends EventEmitter {
username: creds.username,
password: creds.password,
privatekey: creds.privatekey,
keyPassword: creds.keyPassword,
host: creds.host,
port: creds.port
})
@ -170,21 +179,16 @@ class WebSSH2Socket extends EventEmitter {
debug(
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
)
handleError(new SSHConnectionError(`${err.message}`))
this.socket.emit("ssherror", `${err.message}`)
})
// Set up password prompt handler
this.ssh.on("password-prompt", (data) => {
const errorMessage =
err instanceof SSHConnectionError
? err.message
: "SSH connection failed"
this.socket.emit("authentication", {
action: "password_prompt",
message: `Key authentication failed. Please enter password for ${data.username}@${data.host}`
action: "auth_result",
success: false,
message: errorMessage
})
})
this.socket.on("password_response", (password) => {
this.ssh.emit("password-response", password)
})
}
/**

View file

@ -30,24 +30,19 @@ class SSHConnection extends EventEmitter {
* @returns {boolean} - Whether the key appears to be valid
*/
validatePrivateKey(key) {
const keyStart = "-----BEGIN RSA PRIVATE KEY-----"
const keyEnd = "-----END RSA PRIVATE KEY-----"
return (
typeof key === "string" &&
key.includes(keyStart) &&
key.includes(keyEnd) &&
key.trim().length > keyStart.length + keyEnd.length
)
const keyPattern =
/^-----BEGIN (?:RSA )?PRIVATE KEY-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END (?:RSA )?PRIVATE KEY-----\r?\n?$/
return keyPattern.test(key)
}
/**
* Connects to the SSH server using the provided credentials.
* @param {Object} creds - The credentials object containing host, port, username, and optional password.
* @returns {Promise<SSH>} - A promise that resolves with the SSH connection instance.
* Attempts to connect using the provided credentials
* @param {Object} creds - The credentials object
* @returns {Promise<Object>} - A promise that resolves with the SSH connection
*/
connect(creds) {
this.creds = creds
debug("connect: %O", maskSensitiveData(creds))
this.creds = creds
return new Promise((resolve, reject) => {
if (this.conn) {
this.conn.end()
@ -58,6 +53,7 @@ class SSHConnection extends EventEmitter {
// First try with key authentication if available
const sshConfig = this.getSSHConfig(creds, true)
debug("Initial connection config: %O", maskSensitiveData(sshConfig))
this.setupConnectionHandlers(resolve, reject)
@ -80,14 +76,6 @@ class SSHConnection extends EventEmitter {
resolve(this.conn)
})
this.conn.on("end", () => {
debug("connect: end")
})
this.conn.on("close", () => {
debug("connect: close")
})
this.conn.on("error", (err) => {
debug(`connect: error: ${err.message}`)
@ -114,7 +102,6 @@ class SSHConnection extends EventEmitter {
this.setupConnectionHandlers(resolve, reject)
this.conn.connect(passwordConfig)
} else {
// No password available, emit event to request password
debug("No password available, requesting password from client")
this.emit("password-prompt", {
host: this.creds.host,

View file

@ -87,14 +87,9 @@ function getValidatedPort(portInput) {
* - port (number)
* AND either:
* - password (string) OR
* - privatekey (string)
* - privatekey/privateKey (string)
*
* @param {Object} creds - The credentials object.
* @param {string} creds.username - The username.
* @param {string} [creds.password] - The password.
* @param {string} [creds.privatekey] - The private key.
* @param {string} creds.host - The host.
* @param {number} creds.port - The port.
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
*/
function isValidCredentials(creds) {
@ -109,9 +104,10 @@ function isValidCredentials(creds) {
return false
}
// Must have either password or privatekey
// Must have either password or privatekey/privateKey
const hasPassword = typeof creds.password === "string"
const hasPrivateKey = typeof creds.privatekey === "string"
const hasPrivateKey =
typeof creds.privatekey === "string" || typeof creds.privateKey === "string"
return hasPassword || hasPrivateKey
}