1 | // Copyright 2015 Joyent, Inc.
|
---|
2 |
|
---|
3 | module.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 |
|
---|
16 | var assert = require('assert-plus');
|
---|
17 | var Buffer = require('safer-buffer').Buffer;
|
---|
18 | var algs = require('../algs');
|
---|
19 | var utils = require('../utils');
|
---|
20 | var Key = require('../key');
|
---|
21 | var PrivateKey = require('../private-key');
|
---|
22 | var SSHBuffer = require('../ssh-buffer');
|
---|
23 |
|
---|
24 | function 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 |
|
---|
40 | function 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 |
|
---|
56 | function 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 |
|
---|
137 | function 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 | }
|
---|