| // Copyright 2015 Joyent, Inc. |
| |
| module.exports = { |
| read: read.bind(undefined, false, undefined), |
| readType: read.bind(undefined, false), |
| write: write, |
| /* semi-private api, used by sshpk-agent */ |
| readPartial: read.bind(undefined, true), |
| |
| /* shared with ssh format */ |
| readInternal: read, |
| keyTypeToAlg: keyTypeToAlg, |
| algToKeyType: algToKeyType |
| }; |
| |
| var assert = require('assert-plus'); |
| var Buffer = require('safer-buffer').Buffer; |
| var algs = require('../algs'); |
| var utils = require('../utils'); |
| var Key = require('../key'); |
| var PrivateKey = require('../private-key'); |
| var SSHBuffer = require('../ssh-buffer'); |
| |
| function algToKeyType(alg) { |
| assert.string(alg); |
| if (alg === 'ssh-dss') |
| return ('dsa'); |
| else if (alg === 'ssh-rsa') |
| return ('rsa'); |
| else if (alg === 'ssh-ed25519') |
| return ('ed25519'); |
| else if (alg === 'ssh-curve25519') |
| return ('curve25519'); |
| else if (alg.match(/^ecdsa-sha2-/)) |
| return ('ecdsa'); |
| else |
| throw (new Error('Unknown algorithm ' + alg)); |
| } |
| |
| function keyTypeToAlg(key) { |
| assert.object(key); |
| if (key.type === 'dsa') |
| return ('ssh-dss'); |
| else if (key.type === 'rsa') |
| return ('ssh-rsa'); |
| else if (key.type === 'ed25519') |
| return ('ssh-ed25519'); |
| else if (key.type === 'curve25519') |
| return ('ssh-curve25519'); |
| else if (key.type === 'ecdsa') |
| return ('ecdsa-sha2-' + key.part.curve.data.toString()); |
| else |
| throw (new Error('Unknown key type ' + key.type)); |
| } |
| |
| function read(partial, type, buf, options) { |
| if (typeof (buf) === 'string') |
| buf = Buffer.from(buf); |
| assert.buffer(buf, 'buf'); |
| |
| var key = {}; |
| |
| var parts = key.parts = []; |
| var sshbuf = new SSHBuffer({buffer: buf}); |
| |
| var alg = sshbuf.readString(); |
| assert.ok(!sshbuf.atEnd(), 'key must have at least one part'); |
| |
| key.type = algToKeyType(alg); |
| |
| var partCount = algs.info[key.type].parts.length; |
| if (type && type === 'private') |
| partCount = algs.privInfo[key.type].parts.length; |
| |
| while (!sshbuf.atEnd() && parts.length < partCount) |
| parts.push(sshbuf.readPart()); |
| while (!partial && !sshbuf.atEnd()) |
| parts.push(sshbuf.readPart()); |
| |
| assert.ok(parts.length >= 1, |
| 'key must have at least one part'); |
| assert.ok(partial || sshbuf.atEnd(), |
| 'leftover bytes at end of key'); |
| |
| var Constructor = Key; |
| var algInfo = algs.info[key.type]; |
| if (type === 'private' || algInfo.parts.length !== parts.length) { |
| algInfo = algs.privInfo[key.type]; |
| Constructor = PrivateKey; |
| } |
| assert.strictEqual(algInfo.parts.length, parts.length); |
| |
| if (key.type === 'ecdsa') { |
| var res = /^ecdsa-sha2-(.+)$/.exec(alg); |
| assert.ok(res !== null); |
| assert.strictEqual(res[1], parts[0].data.toString()); |
| } |
| |
| var normalized = true; |
| for (var i = 0; i < algInfo.parts.length; ++i) { |
| var p = parts[i]; |
| p.name = algInfo.parts[i]; |
| /* |
| * OpenSSH stores ed25519 "private" keys as seed + public key |
| * concat'd together (k followed by A). We want to keep them |
| * separate for other formats that don't do this. |
| */ |
| if (key.type === 'ed25519' && p.name === 'k') |
| p.data = p.data.slice(0, 32); |
| |
| if (p.name !== 'curve' && algInfo.normalize !== false) { |
| var nd; |
| if (key.type === 'ed25519') { |
| nd = utils.zeroPadToLength(p.data, 32); |
| } else { |
| nd = utils.mpNormalize(p.data); |
| } |
| if (nd.toString('binary') !== |
| p.data.toString('binary')) { |
| p.data = nd; |
| normalized = false; |
| } |
| } |
| } |
| |
| if (normalized) |
| key._rfc4253Cache = sshbuf.toBuffer(); |
| |
| if (partial && typeof (partial) === 'object') { |
| partial.remainder = sshbuf.remainder(); |
| partial.consumed = sshbuf._offset; |
| } |
| |
| return (new Constructor(key)); |
| } |
| |
| function write(key, options) { |
| assert.object(key); |
| |
| var alg = keyTypeToAlg(key); |
| var i; |
| |
| var algInfo = algs.info[key.type]; |
| if (PrivateKey.isPrivateKey(key)) |
| algInfo = algs.privInfo[key.type]; |
| var parts = algInfo.parts; |
| |
| var buf = new SSHBuffer({}); |
| |
| buf.writeString(alg); |
| |
| for (i = 0; i < parts.length; ++i) { |
| var data = key.part[parts[i]].data; |
| if (algInfo.normalize !== false) { |
| if (key.type === 'ed25519') |
| data = utils.zeroPadToLength(data, 32); |
| else |
| data = utils.mpNormalize(data); |
| } |
| if (key.type === 'ed25519' && parts[i] === 'k') |
| data = Buffer.concat([data, key.part.A.data]); |
| buf.writeBuffer(data); |
| } |
| |
| return (buf.toBuffer()); |
| } |