169 lines
3.9 KiB
JavaScript
169 lines
3.9 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
;(function (exports) {
|
|
'use strict';
|
|
|
|
if (!exports.Enc) { exports.Enc = {}; }
|
|
if (!exports.SSH) { exports.SSH = {}; }
|
|
|
|
var Enc = exports.Enc;
|
|
var SSH = exports.SSH;
|
|
|
|
SSH.parse = function (ssh) {
|
|
ssh = SSH.parseBlock(ssh);
|
|
ssh = SSH.parseElements(ssh);
|
|
//delete ssh.bytes;
|
|
return SSH.parsePublicKey(ssh);
|
|
};
|
|
|
|
SSH.parseBlock = function (ssh) {
|
|
ssh = ssh.split(/\s+/g);
|
|
|
|
return {
|
|
type: ssh[0]
|
|
, bytes: Enc.base64ToBuf(ssh[1])
|
|
, comment: ssh[2]
|
|
};
|
|
};
|
|
|
|
SSH.parseElements = function (ssh) {
|
|
var buf = ssh.bytes;
|
|
var fulllen = buf.byteLength || buf.length;
|
|
var offset = (buf.byteOffset || 0);
|
|
var i = 0;
|
|
var index = 0;
|
|
// using dataview to be browser-compatible (I do want _some_ code reuse)
|
|
var dv = new DataView(buf.buffer.slice(offset, offset + fulllen));
|
|
var els = [];
|
|
var el;
|
|
var len;
|
|
|
|
while (index < fulllen) {
|
|
i += 1;
|
|
if (i > 15) { throw new Error("15+ elements, probably not a public ssh key"); }
|
|
len = dv.getUint32(index, false);
|
|
index += 4;
|
|
el = buf.slice(index, index + len);
|
|
// remove BigUInt '00' prefix
|
|
if (0x00 === el[0]) {
|
|
el = el.slice(1);
|
|
}
|
|
els.push(el);
|
|
index += len;
|
|
}
|
|
if (fulllen !== index) {
|
|
throw new Error("invalid ssh public key length \n" + els.map(function (b) {
|
|
return Enc.bufToHex(b);
|
|
}).join('\n'));
|
|
}
|
|
|
|
ssh.elements = els;
|
|
return ssh;
|
|
};
|
|
|
|
SSH.parsePublicKey = function (ssh) {
|
|
var els = ssh.elements;
|
|
var typ = Enc.bufToBin(els[0]);
|
|
var len;
|
|
|
|
// RSA keys are all the same
|
|
if (SSH.types.rsa === typ) {
|
|
ssh.jwk = {
|
|
kty: 'RSA'
|
|
, n: Enc.bufToUrlBase64(els[2])
|
|
, e: Enc.bufToUrlBase64(els[1])
|
|
};
|
|
return ssh;
|
|
}
|
|
|
|
// EC keys are each different
|
|
if (SSH.types.p256 === typ) {
|
|
len = 32;
|
|
ssh.jwk = { kty: 'EC', crv: 'P-256' };
|
|
} else if (SSH.types.p384 === typ) {
|
|
len = 48;
|
|
ssh.jwk = { kty: 'EC', crv: 'P-384' };
|
|
} else {
|
|
throw new Error("Unsupported ssh public key type: "
|
|
+ Enc.bufToBin(els[0]));
|
|
}
|
|
|
|
// els[1] is just a repeat of a subset of els[0]
|
|
var x = els[2].slice(1, 1 + len);
|
|
var y = els[2].slice(1 + len, 1 + len + len);
|
|
|
|
// I don't think EC keys use 0x00 padding, but just in case
|
|
while (0x00 === x[0]) { x = x.slice(1); }
|
|
while (0x00 === y[0]) { y = y.slice(1); }
|
|
|
|
ssh.jwk.x = Enc.bufToUrlBase64(x);
|
|
ssh.jwk.y = Enc.bufToUrlBase64(y);
|
|
|
|
return ssh;
|
|
};
|
|
|
|
SSH.types = {
|
|
// 19 '00000013'
|
|
// e c d s a - s h a 2 - n i s t p 2 5 6
|
|
// 65636473612d736861322d6e69737470323536
|
|
// 6e69737470323536
|
|
p256: 'ecdsa-sha2-nistp256'
|
|
|
|
// 19 '00000013'
|
|
// e c d s a - s h a 2 - n i s t p 3 8 4
|
|
// 65636473612d736861322d6e69737470333834
|
|
// 6e69737470323536
|
|
, p384: 'ecdsa-sha2-nistp384'
|
|
|
|
// 7 '00000007'
|
|
// s s h - r s a
|
|
// 7373682d727361
|
|
, rsa: 'ssh-rsa'
|
|
};
|
|
|
|
Enc.base64ToBuf = function (b64) {
|
|
return Enc.binToBuf(atob(b64));
|
|
};
|
|
|
|
Enc.binToBuf = function (bin) {
|
|
var arr = bin.split('').map(function (ch) {
|
|
return ch.charCodeAt(0);
|
|
});
|
|
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr;
|
|
};
|
|
|
|
Enc.bufToBase64 = function (u8) {
|
|
var bin = '';
|
|
u8.forEach(function (i) {
|
|
bin += String.fromCharCode(i);
|
|
});
|
|
return btoa(bin);
|
|
};
|
|
|
|
Enc.bufToBin = function (buf) {
|
|
var bin = '';
|
|
// cannot use .map() because Uint8Array would return only 0s
|
|
buf.forEach(function (ch) {
|
|
bin += String.fromCharCode(ch);
|
|
});
|
|
return bin;
|
|
};
|
|
|
|
Enc.bufToUrlBase64 = function (u8) {
|
|
return Enc.bufToBase64(u8)
|
|
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
};
|
|
|
|
Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) {
|
|
var r = str % 4;
|
|
if (2 === r) {
|
|
str += '==';
|
|
} else if (3 === r) {
|
|
str += '=';
|
|
}
|
|
return str.replace(/-/g, '+').replace(/_/g, '/');
|
|
};
|
|
|
|
}('undefined' !== typeof window ? window : module.exports));
|