From 796145186b3cc19780552bf7099628a96655d76f Mon Sep 17 00:00:00 2001 From: Bill Church Date: Tue, 3 Dec 2024 20:09:13 +0000 Subject: [PATCH] feat: passphrase encrypted private key authentication #382 --- CONFIG.md | 39 ++++++++++++++++++++++++++++++++++++--- README.md | 12 ++++++++---- app/config.js | 4 +++- app/configSchema.js | 3 ++- app/ssh.js | 27 +++++++++++++++++++++------ 5 files changed, 70 insertions(+), 15 deletions(-) diff --git a/CONFIG.md b/CONFIG.md index 299c45a..68eea3e 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -84,7 +84,7 @@ Renamed and expanded options: ## Detailed Changes ### 1. Authentication Options -- Added support for SSH private key authentication via `user.privateKey` +- Added support for SSH private key authentication via `user.privateKey` and passphrase encrypted private keys via `user.passphrase` - Removed `user.overridebasic` option - Added keyboard-interactive authentication controls @@ -128,7 +128,8 @@ These settings are now managed client-side. "user": { "name": null, "password": null, - "privateKey": null + "privateKey": null, + "passphrase": null }, "ssh": { "host": null, @@ -138,7 +139,39 @@ These settings are now managed client-side. "keepaliveInterval": 120000, "keepaliveCountMax": 10, "algorithms": { - // ... algorithm configurations ... + "cipher": [ + "aes128-ctr", + "aes192-ctr", + "aes256-ctr", + "aes128-gcm", + "aes128-gcm@openssh.com", + "aes256-gcm", + "aes256-gcm@openssh.com", + "aes256-cbc" + ], + "compress": [ + "none", + "zlib@openssh.com", + "zlib" + ], + "hmac": [ + "hmac-sha2-256", + "hmac-sha2-512", + "hmac-sha1" + ], + "kex": [ + "ecdh-sha2-nistp256", + "ecdh-sha2-nistp384", + "ecdh-sha2-nistp521", + "diffie-hellman-group-exchange-sha256", + "diffie-hellman-group14-sha1" + ], + "serverHostKey": [ + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp521", + "ssh-rsa" + ] } }, "options": { diff --git a/README.md b/README.md index 633c84e..b0f7862 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ For more information on SSH keyboard-interactive authentication, refer to [RFC 4 ### SSH Private Key Authentication -WebSSH2 supports SSH private key authentication when using the `/ssh/host/` endpoint with a private key configured in the server settings. +WebSSH2 supports SSH private key authentication when using the `/ssh/host/` endpoint with a private key configured in the server settings or via the interactive method with the `/ssh/` endpoint. #### Configuration @@ -215,13 +215,17 @@ Private key authentication can only be configured through the `config.json` file #### Key Requirements - Only `ssh-rsa` type keys are supported +- Passphrase encryption is supported, and if used the `passphrase` must be provided - The private key must be in PEM format - The key in `config.json` must be on a single line with `\n` as line separators - Must include the appropriate header and footer: ``` -----BEGIN RSA PRIVATE KEY-----\n[... key content ...]\n-----END RSA PRIVATE KEY----- ``` - + or for encrypted keys: + ``` + -----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,5930F19760F7FBBC865400940A89D954\n\n[... key content ...]\n-----END RSA PRIVATE KEY----- + ``` #### Generating a Private Key To generate a new SSH private key, you can use the following command: @@ -231,10 +235,10 @@ ssh-keygen -m PEM -t rsa -b 4096 -f ~/.ssh/id_rsa #### Converting Your Private Key -To convert your existing SSH private key into the correct format for `config.json`, you can use this bash command: +Keys uploaded or pasted using the interactive mode through the `/ssh` endpoint can work as-is, however if using a key with `config.json` you must convert your existing SSH private key into the correct format (single line). A bash one-liner you can to accomplish this is: ```bash -echo '"'$(cat ~/.ssh/id_rsa | tr '\n' '~' | sed 's/~/\\n/g')'"' +echo ' "privateKey": "'$(cat ~/.ssh/id_rsa | tr '\n' '~' | sed 's/~/\\n/g')'"' ``` This command: diff --git a/app/config.js b/app/config.js index 8dcf583..b4c9627 100644 --- a/app/config.js +++ b/app/config.js @@ -22,7 +22,9 @@ const defaultConfig = { }, user: { name: null, - password: null + password: null, + privateKey: null, + passphrase: null }, ssh: { host: null, diff --git a/app/configSchema.js b/app/configSchema.js index ec8231a..a0ca1d3 100644 --- a/app/configSchema.js +++ b/app/configSchema.js @@ -27,7 +27,8 @@ const configSchema = { properties: { name: { type: ["string", "null"] }, password: { type: ["string", "null"] }, - privateKey: { type: ["string", "null"] } + privateKey: { type: ["string", "null"] }, + passphrase: { type: ["string", "null"] } }, required: ["name", "password"] }, diff --git a/app/ssh.js b/app/ssh.js index 57fac9d..d64cfe5 100644 --- a/app/ssh.js +++ b/app/ssh.js @@ -40,6 +40,15 @@ class SSHConnection extends EventEmitter { return standardKeyPattern.test(key) || encryptedKeyPattern.test(key) } + /** + * Checks if a private key is encrypted + * @param {string} key - The private key to check + * @returns {boolean} - Whether the key is encrypted + */ + isEncryptedKey(key) { + return key.includes("Proc-Type: 4,ENCRYPTED") + } + /** * Attempts to connect using the provided credentials * @param {Object} creds - The credentials object @@ -207,9 +216,9 @@ class SSHConnection extends EventEmitter { /** * Generates the SSH configuration object based on credentials. - * @param {Object} creds - The credentials object containing host, port, username, and optional password/privateKey/passphrase. + * @param {Object} creds - The credentials object * @param {boolean} useKey - Whether to attempt key authentication - * @returns {Object} - The SSH configuration object. + * @returns {Object} - The SSH configuration object */ getSSHConfig(creds, useKey) { const config = { @@ -235,10 +244,16 @@ class SSHConnection extends EventEmitter { config.privateKey = privateKey - // Add passphrase if provided - if (creds.passphrase) { - debug("Passphrase provided for private key") - config.passphrase = creds.passphrase + // Check if key is encrypted and passphrase is needed + if (this.isEncryptedKey(privateKey)) { + const passphrase = creds.passphrase || this.config.user.passphrase + if (!passphrase) { + throw new SSHConnectionError( + "Encrypted private key requires a passphrase" + ) + } + debug("Adding passphrase for encrypted private key") + config.passphrase = passphrase } } else if (creds.password) { debug("Using password authentication")