• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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