You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					167 lines
				
				4.1 KiB
			
		
		
			
		
	
	
					167 lines
				
				4.1 KiB
			| 
											3 years ago
										 | // 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()); | ||
|  | } |