From 5a6cde1446fc3569acb5e8c16078544e99d6a4d5 Mon Sep 17 00:00:00 2001 From: Hideki Nakamura Date: Sat, 6 Jan 2018 01:12:01 +0900 Subject: [PATCH] Refactoring ssh-keygen function (#1533) --- package.json | 1 - .../storage/localfilesystem/projects/index.js | 2 + .../storage/localfilesystem/projects/ssh.js | 80 ++++++++++++++++++ .../storage/localfilesystem/sshkeys.js | 45 +++-------- .../storage/localfilesystem/sshkeys_spec.js | 81 ++++++++++++++++--- 5 files changed, 164 insertions(+), 45 deletions(-) create mode 100644 red/runtime/storage/localfilesystem/projects/ssh.js diff --git a/package.json b/package.json index 3d9dbabcd..044d485de 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "raw-body":"2.2.0", "semver": "5.3.0", "sentiment":"2.1.0", - "ssh-keygen":"0.4.1", "uglify-js":"3.0.20", "when": "3.7.8", "ws": "1.1.1", diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index bb012aa09..a6802eeb1 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -23,6 +23,7 @@ var crypto = require('crypto'); var storageSettings = require("../settings"); var util = require("../util"); var gitTools = require("./git"); +var sshTools = require("./ssh"); var Projects = require("./Project"); @@ -89,6 +90,7 @@ function init(_settings, _runtime) { projectsEnabled = false; } else { Projects.init(settings,runtime); + sshTools.init(settings,runtime); projectsDir = fspath.join(settings.userDir,"projects"); if (!settings.readOnly) { return fs.ensureDir(projectsDir) diff --git a/red/runtime/storage/localfilesystem/projects/ssh.js b/red/runtime/storage/localfilesystem/projects/ssh.js new file mode 100644 index 000000000..54a7bfa38 --- /dev/null +++ b/red/runtime/storage/localfilesystem/projects/ssh.js @@ -0,0 +1,80 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var exec = require('child_process').exec; +var spawn = require('child_process').spawn; + +var sshkeygenCommand = "ssh-keygen"; + +var log; + +function runSshKeygenCommand(args,cwd,env) { + // console.log("[run ssh-keygen] args:", args); + return new Promise(function(resolve, reject) { + var child = spawn(sshkeygenCommand, args, {cwd: cwd, detached: true, env: env}); + var stdout = ""; + var stderr = ""; + + child.stdout.on('data', function(data) { + stdout += data; + }); + child.stderr.on('data', function(data) { + stderr += data; + }); + child.on('close', function(code, signal) { + if (code !== 0) { + var err = new Error(stderr); + err.stdout = stdout; + err.stderr = stderr; + if (/passphrase is too short/.test(stderr)) { + err.code = "key_passphrase_too_short"; + } else if(/Key must at least be 1024 bits/.test(stderr)) { + err.code = "key_length_too_short"; + } + reject(err); + } + else { + resolve(stdout); + } + }); + }); +} + +function init(_settings, _runtime) { + log = _runtime.log; +} + +function generateKey(options) { + var args = ['-q', '-t', 'rsa']; + if (options.size) { + args.push('-b', options.size); + } + if (options.location) { + args.push('-f', options.location); + } + if (options.comment) { + args.push('-C', options.comment); + } + if (options.password) { + args.push('-N', options.password); + } + return runSshKeygenCommand(args,__dirname); +} + +module.exports = { + init: init, + generateKey: generateKey, +}; \ No newline at end of file diff --git a/red/runtime/storage/localfilesystem/sshkeys.js b/red/runtime/storage/localfilesystem/sshkeys.js index fa4916ba1..4125d2bd6 100644 --- a/red/runtime/storage/localfilesystem/sshkeys.js +++ b/red/runtime/storage/localfilesystem/sshkeys.js @@ -17,7 +17,7 @@ var fs = require('fs-extra'); var when = require('when'); var fspath = require("path"); -var keygen = require('ssh-keygen'); +var sshTools = require("./projects/ssh"); var settings; var runtime; @@ -97,26 +97,12 @@ function generateSSHKey(username, options) { } else { var comment = options.comment || ""; var password = options.password || ""; - if (password.length > 0 && password.length < 5) { - var e2 = new Error("SSH Key passphrase too short"); - e2.code = "key_passphrase_too_short"; - throw e2; - } var size = options.size || 2048; var sshKeyFileBasename = username + '_' + name; var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename); return generateSSHKeyPair(name, privateKeyFilePath, comment, password, size) } }) - // .then(function(keyfile_name) { - // return checkSSHKeyFileAndGetPublicKeyFileName(username, name) - // .then(function() { - // return keyfile_name; - // }) - // .catch(function(err) { - // throw new Error('Failed to generate ssh key files'); - // }); - // }); } function deleteSSHKey(username, name) { @@ -162,27 +148,22 @@ function deleteSSHKeyFiles(username, name) { return Promise.all([ fs.remove(privateKeyFilePath), fs.remove(publicKeyFilePath) - ]); + ]) + .then(function() { + return true; + }); } function generateSSHKeyPair(name, privateKeyPath, comment, password, size) { log.trace("ssh-keygen["+[name,privateKeyPath,comment,size,"hasPassword?"+!!password].join(",")+"]"); - return new Promise(function(resolve, reject) { - keygen({ - location: privateKeyPath, - comment: comment, - password: password, - size: size - }, function(err, out) { - if ( err ) { - err.code = "key_generation_failed"; - reject(err); - } - else { - resolve(name); - } - }); - }); + return sshTools.generateKey({location: privateKeyPath, comment: comment, password: password, size: size}) + .then(function(stdout) { + return name; + }) + .catch(function(err) { + log.log('[SSHKey generation] error:', err); + throw err; + }); } module.exports = { diff --git a/test/red/runtime/storage/localfilesystem/sshkeys_spec.js b/test/red/runtime/storage/localfilesystem/sshkeys_spec.js index 370ba9908..723ce9e10 100644 --- a/test/red/runtime/storage/localfilesystem/sshkeys_spec.js +++ b/test/red/runtime/storage/localfilesystem/sshkeys_spec.js @@ -29,6 +29,7 @@ describe("storage/localfilesystem/sshkeys", function() { log:{ _:function() { return "placeholder message"}, info: function() { }, + log: function() { }, trace: function() { } } }; @@ -61,7 +62,6 @@ describe("storage/localfilesystem/sshkeys", function() { localfilesystem.init(mockSettings, mockRuntime).then(function() { sshkeys.init(mockSettings, mockRuntime).then(function() { sshkeys.listSSHKeys(username).then(function(retObj) { - console.log('retObj:', retObj); retObj.should.be.instanceOf(Array).and.have.lengthOf(0); done(); }).catch(function(err) { @@ -246,7 +246,31 @@ describe("storage/localfilesystem/sshkeys", function() { var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); var username = 'test'; var options = { - email: 'test@test.com', + name: 'test-key01' + }; + localfilesystem.init(mockSettings, mockRuntime).then(function() { + sshkeys.init(mockSettings, mockRuntime).then(function() { + sshkeys.generateSSHKey(username, options).then(function(retObj) { + retObj.should.be.equal(options.name); + fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true(); + fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true(); + done(); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }); + + it('should generate sshkey file with only comment data', function(done) { + var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); + var username = 'test'; + var options = { + comment: 'test@test.com', name: 'test-key01' }; localfilesystem.init(mockSettings, mockRuntime).then(function() { @@ -271,7 +295,7 @@ describe("storage/localfilesystem/sshkeys", function() { var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); var username = 'test'; var options = { - email: 'test@test.com', + comment: 'test@test.com', name: 'test-key01', password: 'testtest' }; @@ -297,7 +321,7 @@ describe("storage/localfilesystem/sshkeys", function() { var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); var username = 'test'; var options = { - email: 'test@test.com', + comment: 'test@test.com', name: 'test-key01', size: 4096 }; @@ -324,7 +348,7 @@ describe("storage/localfilesystem/sshkeys", function() { var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); var username = 'test'; var options = { - email: 'test@test.com', + comment: 'test@test.com', name: 'test-key01', password: 'testtest', size: 4096 @@ -352,19 +376,52 @@ describe("storage/localfilesystem/sshkeys", function() { var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); var username = 'test'; var options = { - email: 'test@test.com', + comment: 'test@test.com', name: 'test-key01', - size: 3333 + size: 1023 }; localfilesystem.init(mockSettings, mockRuntime).then(function() { sshkeys.init(mockSettings, mockRuntime).then(function() { sshkeys.generateSSHKey(username, options).then(function(retObj) { - retObj.should.be.equal(options.name); - fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name)).should.be.true(); - fs.existsSync(path.join(sshkeyDirPath,username+'_'+options.name+'.pub')).should.be.true(); - done(); + done(new Error('Does NOT throw error!')); }).catch(function(err) { - done(err); + try { + err.should.have.have.property('code', 'key_length_too_short'); + done(); + } + catch (error) { + done(error); + } + }); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }); + + it('should not generate sshkey file with illegal password', function(done) { + this.timeout(5000); + var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys'); + var username = 'test'; + var options = { + comment: 'test@test.com', + name: 'test-key01', + password: 'aa' + }; + localfilesystem.init(mockSettings, mockRuntime).then(function() { + sshkeys.init(mockSettings, mockRuntime).then(function() { + sshkeys.generateSSHKey(username, options).then(function(retObj) { + done(new Error('Does NOT throw error!')); + }).catch(function(err) { + try { + err.should.have.have.property('code', 'key_passphrase_too_short'); + done(); + } + catch (error) { + done(error); + } }); }).catch(function(err) { done(err);