123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- // Copyright 2012 Joyent, Inc. All rights reserved.
- var assert = require('assert-plus');
- var crypto = require('crypto');
- var asn1 = require('asn1');
- var ctype = require('ctype');
- ///--- Helpers
- function readNext(buffer, offset) {
- var len = ctype.ruint32(buffer, 'big', offset);
- offset += 4;
- var newOffset = offset + len;
- return {
- data: buffer.slice(offset, newOffset),
- offset: newOffset
- };
- }
- function writeInt(writer, buffer) {
- writer.writeByte(0x02); // ASN1.Integer
- writer.writeLength(buffer.length);
- for (var i = 0; i < buffer.length; i++)
- writer.writeByte(buffer[i]);
- return writer;
- }
- function rsaToPEM(key) {
- var buffer;
- var der;
- var exponent;
- var i;
- var modulus;
- var newKey = '';
- var offset = 0;
- var type;
- var tmp;
- try {
- buffer = new Buffer(key.split(' ')[1], 'base64');
- tmp = readNext(buffer, offset);
- type = tmp.data.toString();
- offset = tmp.offset;
- if (type !== 'ssh-rsa')
- throw new Error('Invalid ssh key type: ' + type);
- tmp = readNext(buffer, offset);
- exponent = tmp.data;
- offset = tmp.offset;
- tmp = readNext(buffer, offset);
- modulus = tmp.data;
- } catch (e) {
- throw new Error('Invalid ssh key: ' + key);
- }
- // DER is a subset of BER
- der = new asn1.BerWriter();
- der.startSequence();
- der.startSequence();
- der.writeOID('1.2.840.113549.1.1.1');
- der.writeNull();
- der.endSequence();
- der.startSequence(0x03); // bit string
- der.writeByte(0x00);
- // Actual key
- der.startSequence();
- writeInt(der, modulus);
- writeInt(der, exponent);
- der.endSequence();
- // bit string
- der.endSequence();
- der.endSequence();
- tmp = der.buffer.toString('base64');
- for (i = 0; i < tmp.length; i++) {
- if ((i % 64) === 0)
- newKey += '\n';
- newKey += tmp.charAt(i);
- }
- if (!/\\n$/.test(newKey))
- newKey += '\n';
- return '-----BEGIN PUBLIC KEY-----' + newKey + '-----END PUBLIC KEY-----\n';
- }
- function dsaToPEM(key) {
- var buffer;
- var offset = 0;
- var tmp;
- var der;
- var newKey = '';
- var type;
- var p;
- var q;
- var g;
- var y;
- try {
- buffer = new Buffer(key.split(' ')[1], 'base64');
- tmp = readNext(buffer, offset);
- type = tmp.data.toString();
- offset = tmp.offset;
- /* JSSTYLED */
- if (!/^ssh-ds[as].*/.test(type))
- throw new Error('Invalid ssh key type: ' + type);
- tmp = readNext(buffer, offset);
- p = tmp.data;
- offset = tmp.offset;
- tmp = readNext(buffer, offset);
- q = tmp.data;
- offset = tmp.offset;
- tmp = readNext(buffer, offset);
- g = tmp.data;
- offset = tmp.offset;
- tmp = readNext(buffer, offset);
- y = tmp.data;
- } catch (e) {
- console.log(e.stack);
- throw new Error('Invalid ssh key: ' + key);
- }
- // DER is a subset of BER
- der = new asn1.BerWriter();
- der.startSequence();
- der.startSequence();
- der.writeOID('1.2.840.10040.4.1');
- der.startSequence();
- writeInt(der, p);
- writeInt(der, q);
- writeInt(der, g);
- der.endSequence();
- der.endSequence();
- der.startSequence(0x03); // bit string
- der.writeByte(0x00);
- writeInt(der, y);
- der.endSequence();
- der.endSequence();
- tmp = der.buffer.toString('base64');
- for (var i = 0; i < tmp.length; i++) {
- if ((i % 64) === 0)
- newKey += '\n';
- newKey += tmp.charAt(i);
- }
- if (!/\\n$/.test(newKey))
- newKey += '\n';
- return '-----BEGIN PUBLIC KEY-----' + newKey + '-----END PUBLIC KEY-----\n';
- }
- ///--- API
- module.exports = {
- /**
- * Converts an OpenSSH public key (rsa only) to a PKCS#8 PEM file.
- *
- * The intent of this module is to interoperate with OpenSSL only,
- * specifically the node crypto module's `verify` method.
- *
- * @param {String} key an OpenSSH public key.
- * @return {String} PEM encoded form of the RSA public key.
- * @throws {TypeError} on bad input.
- * @throws {Error} on invalid ssh key formatted data.
- */
- sshKeyToPEM: function sshKeyToPEM(key) {
- assert.string(key, 'ssh_key');
- /* JSSTYLED */
- if (/^ssh-rsa.*/.test(key))
- return rsaToPEM(key);
- /* JSSTYLED */
- if (/^ssh-ds[as].*/.test(key))
- return dsaToPEM(key);
- throw new Error('Only RSA and DSA public keys are allowed');
- },
- /**
- * Generates an OpenSSH fingerprint from an ssh public key.
- *
- * @param {String} key an OpenSSH public key.
- * @return {String} key fingerprint.
- * @throws {TypeError} on bad input.
- * @throws {Error} if what you passed doesn't look like an ssh public key.
- */
- fingerprint: function fingerprint(key) {
- assert.string(key, 'ssh_key');
- var pieces = key.split(' ');
- if (!pieces || !pieces.length || pieces.length < 2)
- throw new Error('invalid ssh key');
- var data = new Buffer(pieces[1], 'base64');
- var hash = crypto.createHash('md5');
- hash.update(data);
- var digest = hash.digest('hex');
- var fp = '';
- for (var i = 0; i < digest.length; i++) {
- if (i && i % 2 === 0)
- fp += ':';
- fp += digest[i];
- }
- return fp;
- },
- /**
- * Converts a PKGCS#8 PEM file to an OpenSSH public key (rsa)
- *
- * The reverse of the above function.
- */
- pemToRsaSSHKey: function pemToRsaSSHKey(pem, comment) {
- assert.equal('string', typeof pem, 'typeof pem');
- // chop off the BEGIN PUBLIC KEY and END PUBLIC KEY portion
- var cleaned = pem.split('\n').slice(1, -2).join('');
- var buf = new Buffer(cleaned, 'base64');
- var der = new asn1.BerReader(buf);
- der.readSequence();
- der.readSequence();
- var oid = der.readOID();
- assert.equal(oid, '1.2.840.113549.1.1.1', 'pem not in RSA format');
- // Null -- XXX this probably isn't good practice
- der.readByte();
- der.readByte();
- // bit string sequence
- der.readSequence(0x03);
- der.readByte();
- der.readSequence();
- // modulus
- assert.equal(der.peek(), asn1.Ber.Integer, 'modulus not an integer');
- der._offset = der.readLength(der.offset + 1);
- var modulus = der._buf.slice(der.offset, der.offset + der.length);
- der._offset += der.length;
- // exponent
- assert.equal(der.peek(), asn1.Ber.Integer, 'exponent not an integer');
- der._offset = der.readLength(der.offset + 1);
- var exponent = der._buf.slice(der.offset, der.offset + der.length);
- der._offset += der.length;
- // now, make the key
- var type = new Buffer('ssh-rsa');
- var buffer = new Buffer(4 + type.length + 4 + modulus.length + 4 + exponent.length);
- var i = 0;
- buffer.writeUInt32BE(type.length, i); i += 4;
- type.copy(buffer, i); i += type.length;
- buffer.writeUInt32BE(exponent.length, i); i += 4;
- exponent.copy(buffer, i); i += exponent.length;
- buffer.writeUInt32BE(modulus.length, i); i += 4;
- modulus.copy(buffer, i); i += modulus.length;
- var s = type.toString() + ' ' + buffer.toString('base64') + ' ' + (comment || '');
- return s;
- }
- };
|