| // Copyright 2015 Joyent, Inc. |
| |
| module.exports = { |
| read: read, |
| readSSHPrivate: readSSHPrivate, |
| write: write |
| }; |
| |
| var assert = require('assert-plus'); |
| var asn1 = require('asn1'); |
| var algs = require('../algs'); |
| var utils = require('../utils'); |
| var crypto = require('crypto'); |
| |
| var Key = require('../key'); |
| var PrivateKey = require('../private-key'); |
| var pem = require('./pem'); |
| var rfc4253 = require('./rfc4253'); |
| var SSHBuffer = require('../ssh-buffer'); |
| |
| function read(buf, options) { |
| return (pem.read(buf, options)); |
| } |
| |
| var MAGIC = 'openssh-key-v1'; |
| |
| function readSSHPrivate(type, buf) { |
| buf = new SSHBuffer({buffer: buf}); |
| |
| var magic = buf.readCString(); |
| assert.strictEqual(magic, MAGIC, 'bad magic string'); |
| |
| var cipher = buf.readString(); |
| var kdf = buf.readString(); |
| |
| /* 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(); |
| if (nkeys !== 1) { |
| throw (new Error('OpenSSH-format key file contains ' + |
| 'multiple keys: this is unsupported.')); |
| } |
| |
| var pubKey = buf.readBuffer(); |
| |
| if (type === 'public') { |
| assert.ok(buf.atEnd(), 'excess bytes left after key'); |
| return (rfc4253.read(pubKey)); |
| } |
| |
| var privKeyBlob = buf.readBuffer(); |
| assert.ok(buf.atEnd(), 'excess bytes left after key'); |
| |
| buf = new SSHBuffer({buffer: privKeyBlob}); |
| |
| var checkInt1 = buf.readInt(); |
| var checkInt2 = buf.readInt(); |
| assert.strictEqual(checkInt1, checkInt2, 'checkints do not match'); |
| |
| var ret = {}; |
| var key = rfc4253.readInternal(ret, 'private', buf.remainder()); |
| |
| buf.skip(ret.consumed); |
| |
| var comment = buf.readString(); |
| key.comment = comment; |
| |
| return (key); |
| } |
| |
| function write(key, options) { |
| var pubKey; |
| if (PrivateKey.isPrivateKey(key)) |
| pubKey = key.toPublic(); |
| else |
| pubKey = key; |
| |
| var privBuf; |
| if (PrivateKey.isPrivateKey(key)) { |
| privBuf = new SSHBuffer({}); |
| var checkInt = crypto.randomBytes(4).readUInt32BE(0); |
| privBuf.writeInt(checkInt); |
| privBuf.writeInt(checkInt); |
| privBuf.write(key.toBuffer('rfc4253')); |
| privBuf.writeString(key.comment || ''); |
| |
| var n = 1; |
| while (privBuf._offset % 8 !== 0) |
| privBuf.writeChar(n++); |
| } |
| |
| var buf = new SSHBuffer({}); |
| |
| buf.writeCString(MAGIC); |
| buf.writeString('none'); /* cipher */ |
| buf.writeString('none'); /* kdf */ |
| buf.writeBuffer(new Buffer(0)); /* kdfoptions */ |
| |
| buf.writeInt(1); /* nkeys */ |
| buf.writeBuffer(pubKey.toBuffer('rfc4253')); |
| |
| if (privBuf) |
| buf.writeBuffer(privBuf.toBuffer()); |
| |
| buf = buf.toBuffer(); |
| |
| var header; |
| if (PrivateKey.isPrivateKey(key)) |
| header = 'OPENSSH PRIVATE KEY'; |
| else |
| header = 'OPENSSH PUBLIC KEY'; |
| |
| var tmp = buf.toString('base64'); |
| var len = tmp.length + (tmp.length / 70) + |
| 18 + 16 + header.length*2 + 10; |
| buf = new Buffer(len); |
| var o = 0; |
| o += buf.write('-----BEGIN ' + header + '-----\n', o); |
| for (var i = 0; i < tmp.length; ) { |
| var limit = i + 70; |
| if (limit > tmp.length) |
| limit = tmp.length; |
| o += buf.write(tmp.slice(i, limit), o); |
| buf[o++] = 10; |
| i = limit; |
| } |
| o += buf.write('-----END ' + header + '-----\n', o); |
| |
| return (buf.slice(0, o)); |
| } |