sshpk
Advanced tools
Comparing version 1.9.2 to 1.10.0
@@ -110,5 +110,5 @@ // Copyright 2015 Joyent, Inc. | ||
if (alg && alg.toLowerCase() === 'openssh') | ||
return (sshpriv.readSSHPrivate(type, buf)); | ||
return (sshpriv.readSSHPrivate(type, buf, options)); | ||
if (alg && alg.toLowerCase() === 'ssh2') | ||
return (rfc4253.readType(type, buf)); | ||
return (rfc4253.readType(type, buf, options)); | ||
@@ -115,0 +115,0 @@ var der = new asn1.BerReader(buf); |
@@ -20,3 +20,6 @@ // Copyright 2015 Joyent, Inc. | ||
var SSHBuffer = require('../ssh-buffer'); | ||
var errors = require('../errors'); | ||
var bcrypt; | ||
function read(buf, options) { | ||
@@ -28,3 +31,3 @@ return (pem.read(buf, options)); | ||
function readSSHPrivate(type, buf) { | ||
function readSSHPrivate(type, buf, options) { | ||
buf = new SSHBuffer({buffer: buf}); | ||
@@ -37,13 +40,4 @@ | ||
var kdf = buf.readString(); | ||
var kdfOpts = buf.readBuffer(); | ||
/* We only support unencrypted keys. */ | ||
if (cipher !== 'none' || kdf !== 'none') { | ||
throw (new Error('OpenSSH-format key is encrypted ' + | ||
'(password-protected). Please use the SSH agent ' + | ||
'or decrypt the key.')); | ||
} | ||
/* Skip over kdfoptions. */ | ||
buf.readString(); | ||
var nkeys = buf.readInt(); | ||
@@ -65,2 +59,62 @@ if (nkeys !== 1) { | ||
var kdfOptsBuf = new SSHBuffer({ buffer: kdfOpts }); | ||
switch (kdf) { | ||
case 'none': | ||
if (cipher !== 'none') { | ||
throw (new Error('OpenSSH-format key uses KDF "none" ' + | ||
'but specifies a cipher other than "none"')); | ||
} | ||
break; | ||
case 'bcrypt': | ||
var salt = kdfOptsBuf.readBuffer(); | ||
var rounds = kdfOptsBuf.readInt(); | ||
var cinf = utils.opensshCipherInfo(cipher); | ||
if (bcrypt === undefined) { | ||
bcrypt = require('bcrypt-pbkdf'); | ||
} | ||
if (typeof (options.passphrase) === 'string') { | ||
options.passphrase = new Buffer(options.passphrase, | ||
'utf-8'); | ||
} | ||
if (!Buffer.isBuffer(options.passphrase)) { | ||
throw (new errors.KeyEncryptedError( | ||
options.filename, 'OpenSSH')); | ||
} | ||
var pass = new Uint8Array(options.passphrase); | ||
var salti = new Uint8Array(salt); | ||
/* Use the pbkdf to derive both the key and the IV. */ | ||
var out = new Uint8Array(cinf.keySize + cinf.blockSize); | ||
var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length, | ||
out, out.length, rounds); | ||
if (res !== 0) { | ||
throw (new Error('bcrypt_pbkdf function returned ' + | ||
'failure, parameters invalid')); | ||
} | ||
out = new Buffer(out); | ||
var ckey = out.slice(0, cinf.keySize); | ||
var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize); | ||
var cipherStream = crypto.createDecipheriv(cinf.opensslName, | ||
ckey, iv); | ||
cipherStream.setAutoPadding(false); | ||
var chunk, chunks = []; | ||
cipherStream.once('error', function (e) { | ||
if (e.toString().indexOf('bad decrypt') !== -1) { | ||
throw (new Error('Incorrect passphrase ' + | ||
'supplied, could not decrypt key')); | ||
} | ||
throw (e); | ||
}); | ||
cipherStream.write(privKeyBlob); | ||
cipherStream.end(); | ||
while ((chunk = cipherStream.read()) !== null) | ||
chunks.push(chunk); | ||
privKeyBlob = Buffer.concat(chunks); | ||
break; | ||
default: | ||
throw (new Error( | ||
'OpenSSH-format key uses unknown KDF "' + kdf + '"')); | ||
} | ||
buf = new SSHBuffer({buffer: privKeyBlob}); | ||
@@ -70,3 +124,6 @@ | ||
var checkInt2 = buf.readInt(); | ||
assert.strictEqual(checkInt1, checkInt2, 'checkints do not match'); | ||
if (checkInt1 !== checkInt2) { | ||
throw (new Error('Incorrect passphrase supplied, could not ' + | ||
'decrypt key')); | ||
} | ||
@@ -91,2 +148,22 @@ var ret = {}; | ||
var cipher = 'none'; | ||
var kdf = 'none'; | ||
var kdfopts = new Buffer(0); | ||
var cinf = { blockSize: 8 }; | ||
var passphrase; | ||
if (options !== undefined) { | ||
passphrase = options.passphrase; | ||
if (typeof (passphrase) === 'string') | ||
passphrase = new Buffer(passphrase, 'utf-8'); | ||
if (passphrase !== undefined) { | ||
assert.buffer(passphrase, 'options.passphrase'); | ||
assert.optionalString(options.cipher, 'options.cipher'); | ||
cipher = options.cipher; | ||
if (cipher === undefined) | ||
cipher = 'aes128-ctr'; | ||
cinf = utils.opensshCipherInfo(cipher); | ||
kdf = 'bcrypt'; | ||
} | ||
} | ||
var privBuf; | ||
@@ -102,12 +179,58 @@ if (PrivateKey.isPrivateKey(key)) { | ||
var n = 1; | ||
while (privBuf._offset % 8 !== 0) | ||
while (privBuf._offset % cinf.blockSize !== 0) | ||
privBuf.writeChar(n++); | ||
privBuf = privBuf.toBuffer(); | ||
} | ||
switch (kdf) { | ||
case 'none': | ||
break; | ||
case 'bcrypt': | ||
var salt = crypto.randomBytes(16); | ||
var rounds = 16; | ||
var kdfssh = new SSHBuffer({}); | ||
kdfssh.writeBuffer(salt); | ||
kdfssh.writeInt(rounds); | ||
kdfopts = kdfssh.toBuffer(); | ||
if (bcrypt === undefined) { | ||
bcrypt = require('bcrypt-pbkdf'); | ||
} | ||
var pass = new Uint8Array(passphrase); | ||
var salti = new Uint8Array(salt); | ||
/* Use the pbkdf to derive both the key and the IV. */ | ||
var out = new Uint8Array(cinf.keySize + cinf.blockSize); | ||
var res = bcrypt.pbkdf(pass, pass.length, salti, salti.length, | ||
out, out.length, rounds); | ||
if (res !== 0) { | ||
throw (new Error('bcrypt_pbkdf function returned ' + | ||
'failure, parameters invalid')); | ||
} | ||
out = new Buffer(out); | ||
var ckey = out.slice(0, cinf.keySize); | ||
var iv = out.slice(cinf.keySize, cinf.keySize + cinf.blockSize); | ||
var cipherStream = crypto.createCipheriv(cinf.opensslName, | ||
ckey, iv); | ||
cipherStream.setAutoPadding(false); | ||
var chunk, chunks = []; | ||
cipherStream.once('error', function (e) { | ||
throw (e); | ||
}); | ||
cipherStream.write(privBuf); | ||
cipherStream.end(); | ||
while ((chunk = cipherStream.read()) !== null) | ||
chunks.push(chunk); | ||
privBuf = Buffer.concat(chunks); | ||
break; | ||
default: | ||
throw (new Error('Unsupported kdf ' + kdf)); | ||
} | ||
var buf = new SSHBuffer({}); | ||
buf.writeCString(MAGIC); | ||
buf.writeString('none'); /* cipher */ | ||
buf.writeString('none'); /* kdf */ | ||
buf.writeBuffer(new Buffer(0)); /* kdfoptions */ | ||
buf.writeString(cipher); /* cipher */ | ||
buf.writeString(kdf); /* kdf */ | ||
buf.writeBuffer(kdfopts); /* kdfoptions */ | ||
@@ -118,3 +241,3 @@ buf.writeInt(1); /* nkeys */ | ||
if (privBuf) | ||
buf.writeBuffer(privBuf.toBuffer()); | ||
buf.writeBuffer(privBuf); | ||
@@ -121,0 +244,0 @@ buf = buf.toBuffer(); |
@@ -12,3 +12,4 @@ // Copyright 2015 Joyent, Inc. | ||
isCompatible: isCompatible, | ||
opensslKeyDeriv: opensslKeyDeriv | ||
opensslKeyDeriv: opensslKeyDeriv, | ||
opensshCipherInfo: opensshCipherInfo | ||
}; | ||
@@ -248,1 +249,42 @@ | ||
} | ||
function opensshCipherInfo(cipher) { | ||
var inf = {}; | ||
switch (cipher) { | ||
case '3des-cbc': | ||
inf.keySize = 24; | ||
inf.blockSize = 8; | ||
inf.opensslName = 'des-ede3-cbc'; | ||
break; | ||
case 'blowfish-cbc': | ||
inf.keySize = 16; | ||
inf.blockSize = 8; | ||
inf.opensslName = 'bf-cbc'; | ||
break; | ||
case 'aes128-cbc': | ||
case 'aes128-ctr': | ||
case 'aes128-gcm@openssh.com': | ||
inf.keySize = 16; | ||
inf.blockSize = 16; | ||
inf.opensslName = 'aes-128-' + cipher.slice(7, 10); | ||
break; | ||
case 'aes192-cbc': | ||
case 'aes192-ctr': | ||
case 'aes192-gcm@openssh.com': | ||
inf.keySize = 24; | ||
inf.blockSize = 16; | ||
inf.opensslName = 'aes-192-' + cipher.slice(7, 10); | ||
break; | ||
case 'aes256-cbc': | ||
case 'aes256-ctr': | ||
case 'aes256-gcm@openssh.com': | ||
inf.keySize = 32; | ||
inf.blockSize = 16; | ||
inf.opensslName = 'aes-256-' + cipher.slice(7, 10); | ||
break; | ||
default: | ||
throw (new Error( | ||
'Unsupported openssl cipher "' + cipher + '"')); | ||
} | ||
return (inf); | ||
} |
{ | ||
"name": "sshpk", | ||
"version": "1.9.2", | ||
"version": "1.10.0", | ||
"description": "A library for finding and using SSH public keys", | ||
@@ -51,3 +51,4 @@ "main": "lib/index.js", | ||
"jodid25519": "^1.0.0", | ||
"ecc-jsbn": "~0.1.1" | ||
"ecc-jsbn": "~0.1.1", | ||
"bcrypt-pbkdf": "^1.0.0" | ||
}, | ||
@@ -54,0 +55,0 @@ "devDependencies": { |
Sorry, the diff of this file is not supported yet
173037
4324
14