1// Copyright 2015 Joyent, Inc. 2 3module.exports = { 4 read: read.bind(undefined, false, undefined), 5 readType: read.bind(undefined, false), 6 write: write, 7 /* semi-private api, used by sshpk-agent */ 8 readPartial: read.bind(undefined, true), 9 10 /* shared with ssh format */ 11 readInternal: read, 12 keyTypeToAlg: keyTypeToAlg, 13 algToKeyType: algToKeyType 14}; 15 16var assert = require('assert-plus'); 17var Buffer = require('safer-buffer').Buffer; 18var algs = require('../algs'); 19var utils = require('../utils'); 20var Key = require('../key'); 21var PrivateKey = require('../private-key'); 22var SSHBuffer = require('../ssh-buffer'); 23 24function algToKeyType(alg) { 25 assert.string(alg); 26 if (alg === 'ssh-dss') 27 return ('dsa'); 28 else if (alg === 'ssh-rsa') 29 return ('rsa'); 30 else if (alg === 'ssh-ed25519') 31 return ('ed25519'); 32 else if (alg === 'ssh-curve25519') 33 return ('curve25519'); 34 else if (alg.match(/^ecdsa-sha2-/)) 35 return ('ecdsa'); 36 else 37 throw (new Error('Unknown algorithm ' + alg)); 38} 39 40function keyTypeToAlg(key) { 41 assert.object(key); 42 if (key.type === 'dsa') 43 return ('ssh-dss'); 44 else if (key.type === 'rsa') 45 return ('ssh-rsa'); 46 else if (key.type === 'ed25519') 47 return ('ssh-ed25519'); 48 else if (key.type === 'curve25519') 49 return ('ssh-curve25519'); 50 else if (key.type === 'ecdsa') 51 return ('ecdsa-sha2-' + key.part.curve.data.toString()); 52 else 53 throw (new Error('Unknown key type ' + key.type)); 54} 55 56function read(partial, type, buf, options) { 57 if (typeof (buf) === 'string') 58 buf = Buffer.from(buf); 59 assert.buffer(buf, 'buf'); 60 61 var key = {}; 62 63 var parts = key.parts = []; 64 var sshbuf = new SSHBuffer({buffer: buf}); 65 66 var alg = sshbuf.readString(); 67 assert.ok(!sshbuf.atEnd(), 'key must have at least one part'); 68 69 key.type = algToKeyType(alg); 70 71 var partCount = algs.info[key.type].parts.length; 72 if (type && type === 'private') 73 partCount = algs.privInfo[key.type].parts.length; 74 75 while (!sshbuf.atEnd() && parts.length < partCount) 76 parts.push(sshbuf.readPart()); 77 while (!partial && !sshbuf.atEnd()) 78 parts.push(sshbuf.readPart()); 79 80 assert.ok(parts.length >= 1, 81 'key must have at least one part'); 82 assert.ok(partial || sshbuf.atEnd(), 83 'leftover bytes at end of key'); 84 85 var Constructor = Key; 86 var algInfo = algs.info[key.type]; 87 if (type === 'private' || algInfo.parts.length !== parts.length) { 88 algInfo = algs.privInfo[key.type]; 89 Constructor = PrivateKey; 90 } 91 assert.strictEqual(algInfo.parts.length, parts.length); 92 93 if (key.type === 'ecdsa') { 94 var res = /^ecdsa-sha2-(.+)$/.exec(alg); 95 assert.ok(res !== null); 96 assert.strictEqual(res[1], parts[0].data.toString()); 97 } 98 99 var normalized = true; 100 for (var i = 0; i < algInfo.parts.length; ++i) { 101 var p = parts[i]; 102 p.name = algInfo.parts[i]; 103 /* 104 * OpenSSH stores ed25519 "private" keys as seed + public key 105 * concat'd together (k followed by A). We want to keep them 106 * separate for other formats that don't do this. 107 */ 108 if (key.type === 'ed25519' && p.name === 'k') 109 p.data = p.data.slice(0, 32); 110 111 if (p.name !== 'curve' && algInfo.normalize !== false) { 112 var nd; 113 if (key.type === 'ed25519') { 114 nd = utils.zeroPadToLength(p.data, 32); 115 } else { 116 nd = utils.mpNormalize(p.data); 117 } 118 if (nd.toString('binary') !== 119 p.data.toString('binary')) { 120 p.data = nd; 121 normalized = false; 122 } 123 } 124 } 125 126 if (normalized) 127 key._rfc4253Cache = sshbuf.toBuffer(); 128 129 if (partial && typeof (partial) === 'object') { 130 partial.remainder = sshbuf.remainder(); 131 partial.consumed = sshbuf._offset; 132 } 133 134 return (new Constructor(key)); 135} 136 137function write(key, options) { 138 assert.object(key); 139 140 var alg = keyTypeToAlg(key); 141 var i; 142 143 var algInfo = algs.info[key.type]; 144 if (PrivateKey.isPrivateKey(key)) 145 algInfo = algs.privInfo[key.type]; 146 var parts = algInfo.parts; 147 148 var buf = new SSHBuffer({}); 149 150 buf.writeString(alg); 151 152 for (i = 0; i < parts.length; ++i) { 153 var data = key.part[parts[i]].data; 154 if (algInfo.normalize !== false) { 155 if (key.type === 'ed25519') 156 data = utils.zeroPadToLength(data, 32); 157 else 158 data = utils.mpNormalize(data); 159 } 160 if (key.type === 'ed25519' && parts[i] === 'k') 161 data = Buffer.concat([data, key.part.A.data]); 162 buf.writeBuffer(data); 163 } 164 165 return (buf.toBuffer()); 166} 167