Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81146 views
1
// Copyright 2012 Joyent, Inc. All rights reserved.
2
3
var assert = require('assert-plus');
4
var crypto = require('crypto');
5
6
var asn1 = require('asn1');
7
var ctype = require('ctype');
8
9
10
11
///--- Helpers
12
13
function readNext(buffer, offset) {
14
var len = ctype.ruint32(buffer, 'big', offset);
15
offset += 4;
16
17
var newOffset = offset + len;
18
19
return {
20
data: buffer.slice(offset, newOffset),
21
offset: newOffset
22
};
23
}
24
25
26
function writeInt(writer, buffer) {
27
writer.writeByte(0x02); // ASN1.Integer
28
writer.writeLength(buffer.length);
29
30
for (var i = 0; i < buffer.length; i++)
31
writer.writeByte(buffer[i]);
32
33
return writer;
34
}
35
36
37
function rsaToPEM(key) {
38
var buffer;
39
var der;
40
var exponent;
41
var i;
42
var modulus;
43
var newKey = '';
44
var offset = 0;
45
var type;
46
var tmp;
47
48
try {
49
buffer = new Buffer(key.split(' ')[1], 'base64');
50
51
tmp = readNext(buffer, offset);
52
type = tmp.data.toString();
53
offset = tmp.offset;
54
55
if (type !== 'ssh-rsa')
56
throw new Error('Invalid ssh key type: ' + type);
57
58
tmp = readNext(buffer, offset);
59
exponent = tmp.data;
60
offset = tmp.offset;
61
62
tmp = readNext(buffer, offset);
63
modulus = tmp.data;
64
} catch (e) {
65
throw new Error('Invalid ssh key: ' + key);
66
}
67
68
// DER is a subset of BER
69
der = new asn1.BerWriter();
70
71
der.startSequence();
72
73
der.startSequence();
74
der.writeOID('1.2.840.113549.1.1.1');
75
der.writeNull();
76
der.endSequence();
77
78
der.startSequence(0x03); // bit string
79
der.writeByte(0x00);
80
81
// Actual key
82
der.startSequence();
83
writeInt(der, modulus);
84
writeInt(der, exponent);
85
der.endSequence();
86
87
// bit string
88
der.endSequence();
89
90
der.endSequence();
91
92
tmp = der.buffer.toString('base64');
93
for (i = 0; i < tmp.length; i++) {
94
if ((i % 64) === 0)
95
newKey += '\n';
96
newKey += tmp.charAt(i);
97
}
98
99
if (!/\\n$/.test(newKey))
100
newKey += '\n';
101
102
return '-----BEGIN PUBLIC KEY-----' + newKey + '-----END PUBLIC KEY-----\n';
103
}
104
105
106
function dsaToPEM(key) {
107
var buffer;
108
var offset = 0;
109
var tmp;
110
var der;
111
var newKey = '';
112
113
var type;
114
var p;
115
var q;
116
var g;
117
var y;
118
119
try {
120
buffer = new Buffer(key.split(' ')[1], 'base64');
121
122
tmp = readNext(buffer, offset);
123
type = tmp.data.toString();
124
offset = tmp.offset;
125
126
/* JSSTYLED */
127
if (!/^ssh-ds[as].*/.test(type))
128
throw new Error('Invalid ssh key type: ' + type);
129
130
tmp = readNext(buffer, offset);
131
p = tmp.data;
132
offset = tmp.offset;
133
134
tmp = readNext(buffer, offset);
135
q = tmp.data;
136
offset = tmp.offset;
137
138
tmp = readNext(buffer, offset);
139
g = tmp.data;
140
offset = tmp.offset;
141
142
tmp = readNext(buffer, offset);
143
y = tmp.data;
144
} catch (e) {
145
console.log(e.stack);
146
throw new Error('Invalid ssh key: ' + key);
147
}
148
149
// DER is a subset of BER
150
der = new asn1.BerWriter();
151
152
der.startSequence();
153
154
der.startSequence();
155
der.writeOID('1.2.840.10040.4.1');
156
157
der.startSequence();
158
writeInt(der, p);
159
writeInt(der, q);
160
writeInt(der, g);
161
der.endSequence();
162
163
der.endSequence();
164
165
der.startSequence(0x03); // bit string
166
der.writeByte(0x00);
167
writeInt(der, y);
168
der.endSequence();
169
170
der.endSequence();
171
172
tmp = der.buffer.toString('base64');
173
for (var i = 0; i < tmp.length; i++) {
174
if ((i % 64) === 0)
175
newKey += '\n';
176
newKey += tmp.charAt(i);
177
}
178
179
if (!/\\n$/.test(newKey))
180
newKey += '\n';
181
182
return '-----BEGIN PUBLIC KEY-----' + newKey + '-----END PUBLIC KEY-----\n';
183
}
184
185
186
///--- API
187
188
module.exports = {
189
190
/**
191
* Converts an OpenSSH public key (rsa only) to a PKCS#8 PEM file.
192
*
193
* The intent of this module is to interoperate with OpenSSL only,
194
* specifically the node crypto module's `verify` method.
195
*
196
* @param {String} key an OpenSSH public key.
197
* @return {String} PEM encoded form of the RSA public key.
198
* @throws {TypeError} on bad input.
199
* @throws {Error} on invalid ssh key formatted data.
200
*/
201
sshKeyToPEM: function sshKeyToPEM(key) {
202
assert.string(key, 'ssh_key');
203
204
/* JSSTYLED */
205
if (/^ssh-rsa.*/.test(key))
206
return rsaToPEM(key);
207
208
/* JSSTYLED */
209
if (/^ssh-ds[as].*/.test(key))
210
return dsaToPEM(key);
211
212
throw new Error('Only RSA and DSA public keys are allowed');
213
},
214
215
216
/**
217
* Generates an OpenSSH fingerprint from an ssh public key.
218
*
219
* @param {String} key an OpenSSH public key.
220
* @return {String} key fingerprint.
221
* @throws {TypeError} on bad input.
222
* @throws {Error} if what you passed doesn't look like an ssh public key.
223
*/
224
fingerprint: function fingerprint(key) {
225
assert.string(key, 'ssh_key');
226
227
var pieces = key.split(' ');
228
if (!pieces || !pieces.length || pieces.length < 2)
229
throw new Error('invalid ssh key');
230
231
var data = new Buffer(pieces[1], 'base64');
232
233
var hash = crypto.createHash('md5');
234
hash.update(data);
235
var digest = hash.digest('hex');
236
237
var fp = '';
238
for (var i = 0; i < digest.length; i++) {
239
if (i && i % 2 === 0)
240
fp += ':';
241
242
fp += digest[i];
243
}
244
245
return fp;
246
},
247
248
/**
249
* Converts a PKGCS#8 PEM file to an OpenSSH public key (rsa)
250
*
251
* The reverse of the above function.
252
*/
253
pemToRsaSSHKey: function pemToRsaSSHKey(pem, comment) {
254
assert.equal('string', typeof (pem), 'typeof pem');
255
256
// chop off the BEGIN PUBLIC KEY and END PUBLIC KEY portion
257
var cleaned = pem.split('\n').slice(1, -2).join('');
258
259
var buf = new Buffer(cleaned, 'base64');
260
261
var der = new asn1.BerReader(buf);
262
263
der.readSequence();
264
der.readSequence();
265
266
var oid = der.readOID();
267
assert.equal(oid, '1.2.840.113549.1.1.1', 'pem not in RSA format');
268
269
// Null -- XXX this probably isn't good practice
270
der.readByte();
271
der.readByte();
272
273
// bit string sequence
274
der.readSequence(0x03);
275
der.readByte();
276
der.readSequence();
277
278
// modulus
279
assert.equal(der.peek(), asn1.Ber.Integer, 'modulus not an integer');
280
der._offset = der.readLength(der.offset + 1);
281
var modulus = der._buf.slice(der.offset, der.offset + der.length);
282
der._offset += der.length;
283
284
// exponent
285
assert.equal(der.peek(), asn1.Ber.Integer, 'exponent not an integer');
286
der._offset = der.readLength(der.offset + 1);
287
var exponent = der._buf.slice(der.offset, der.offset + der.length);
288
der._offset += der.length;
289
290
// now, make the key
291
var type = new Buffer('ssh-rsa');
292
var buffer = new Buffer(4 + type.length + 4 + modulus.length +
293
4 + exponent.length);
294
var i = 0;
295
buffer.writeUInt32BE(type.length, i); i += 4;
296
type.copy(buffer, i); i += type.length;
297
buffer.writeUInt32BE(exponent.length, i); i += 4;
298
exponent.copy(buffer, i); i += exponent.length;
299
buffer.writeUInt32BE(modulus.length, i); i += 4;
300
modulus.copy(buffer, i); i += modulus.length;
301
302
var s = (type.toString() + ' ' + buffer.toString('base64') + ' ' +
303
(comment || ''));
304
return s;
305
}
306
};
307
308