twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

openssh-cert.js (8752B)


      1 // Copyright 2017 Joyent, Inc.
      2 
      3 module.exports = {
      4 	read: read,
      5 	verify: verify,
      6 	sign: sign,
      7 	signAsync: signAsync,
      8 	write: write,
      9 
     10 	/* Internal private API */
     11 	fromBuffer: fromBuffer,
     12 	toBuffer: toBuffer
     13 };
     14 
     15 var assert = require('assert-plus');
     16 var SSHBuffer = require('../ssh-buffer');
     17 var crypto = require('crypto');
     18 var Buffer = require('safer-buffer').Buffer;
     19 var algs = require('../algs');
     20 var Key = require('../key');
     21 var PrivateKey = require('../private-key');
     22 var Identity = require('../identity');
     23 var rfc4253 = require('./rfc4253');
     24 var Signature = require('../signature');
     25 var utils = require('../utils');
     26 var Certificate = require('../certificate');
     27 
     28 function verify(cert, key) {
     29 	/*
     30 	 * We always give an issuerKey, so if our verify() is being called then
     31 	 * there was no signature. Return false.
     32 	 */
     33 	return (false);
     34 }
     35 
     36 var TYPES = {
     37 	'user': 1,
     38 	'host': 2
     39 };
     40 Object.keys(TYPES).forEach(function (k) { TYPES[TYPES[k]] = k; });
     41 
     42 var ECDSA_ALGO = /^ecdsa-sha2-([^@-]+)-cert-v01@openssh.com$/;
     43 
     44 function read(buf, options) {
     45 	if (Buffer.isBuffer(buf))
     46 		buf = buf.toString('ascii');
     47 	var parts = buf.trim().split(/[ \t\n]+/g);
     48 	if (parts.length < 2 || parts.length > 3)
     49 		throw (new Error('Not a valid SSH certificate line'));
     50 
     51 	var algo = parts[0];
     52 	var data = parts[1];
     53 
     54 	data = Buffer.from(data, 'base64');
     55 	return (fromBuffer(data, algo));
     56 }
     57 
     58 function fromBuffer(data, algo, partial) {
     59 	var sshbuf = new SSHBuffer({ buffer: data });
     60 	var innerAlgo = sshbuf.readString();
     61 	if (algo !== undefined && innerAlgo !== algo)
     62 		throw (new Error('SSH certificate algorithm mismatch'));
     63 	if (algo === undefined)
     64 		algo = innerAlgo;
     65 
     66 	var cert = {};
     67 	cert.signatures = {};
     68 	cert.signatures.openssh = {};
     69 
     70 	cert.signatures.openssh.nonce = sshbuf.readBuffer();
     71 
     72 	var key = {};
     73 	var parts = (key.parts = []);
     74 	key.type = getAlg(algo);
     75 
     76 	var partCount = algs.info[key.type].parts.length;
     77 	while (parts.length < partCount)
     78 		parts.push(sshbuf.readPart());
     79 	assert.ok(parts.length >= 1, 'key must have at least one part');
     80 
     81 	var algInfo = algs.info[key.type];
     82 	if (key.type === 'ecdsa') {
     83 		var res = ECDSA_ALGO.exec(algo);
     84 		assert.ok(res !== null);
     85 		assert.strictEqual(res[1], parts[0].data.toString());
     86 	}
     87 
     88 	for (var i = 0; i < algInfo.parts.length; ++i) {
     89 		parts[i].name = algInfo.parts[i];
     90 		if (parts[i].name !== 'curve' &&
     91 		    algInfo.normalize !== false) {
     92 			var p = parts[i];
     93 			p.data = utils.mpNormalize(p.data);
     94 		}
     95 	}
     96 
     97 	cert.subjectKey = new Key(key);
     98 
     99 	cert.serial = sshbuf.readInt64();
    100 
    101 	var type = TYPES[sshbuf.readInt()];
    102 	assert.string(type, 'valid cert type');
    103 
    104 	cert.signatures.openssh.keyId = sshbuf.readString();
    105 
    106 	var principals = [];
    107 	var pbuf = sshbuf.readBuffer();
    108 	var psshbuf = new SSHBuffer({ buffer: pbuf });
    109 	while (!psshbuf.atEnd())
    110 		principals.push(psshbuf.readString());
    111 	if (principals.length === 0)
    112 		principals = ['*'];
    113 
    114 	cert.subjects = principals.map(function (pr) {
    115 		if (type === 'user')
    116 			return (Identity.forUser(pr));
    117 		else if (type === 'host')
    118 			return (Identity.forHost(pr));
    119 		throw (new Error('Unknown identity type ' + type));
    120 	});
    121 
    122 	cert.validFrom = int64ToDate(sshbuf.readInt64());
    123 	cert.validUntil = int64ToDate(sshbuf.readInt64());
    124 
    125 	var exts = [];
    126 	var extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
    127 	var ext;
    128 	while (!extbuf.atEnd()) {
    129 		ext = { critical: true };
    130 		ext.name = extbuf.readString();
    131 		ext.data = extbuf.readBuffer();
    132 		exts.push(ext);
    133 	}
    134 	extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
    135 	while (!extbuf.atEnd()) {
    136 		ext = { critical: false };
    137 		ext.name = extbuf.readString();
    138 		ext.data = extbuf.readBuffer();
    139 		exts.push(ext);
    140 	}
    141 	cert.signatures.openssh.exts = exts;
    142 
    143 	/* reserved */
    144 	sshbuf.readBuffer();
    145 
    146 	var signingKeyBuf = sshbuf.readBuffer();
    147 	cert.issuerKey = rfc4253.read(signingKeyBuf);
    148 
    149 	/*
    150 	 * OpenSSH certs don't give the identity of the issuer, just their
    151 	 * public key. So, we use an Identity that matches anything. The
    152 	 * isSignedBy() function will later tell you if the key matches.
    153 	 */
    154 	cert.issuer = Identity.forHost('**');
    155 
    156 	var sigBuf = sshbuf.readBuffer();
    157 	cert.signatures.openssh.signature =
    158 	    Signature.parse(sigBuf, cert.issuerKey.type, 'ssh');
    159 
    160 	if (partial !== undefined) {
    161 		partial.remainder = sshbuf.remainder();
    162 		partial.consumed = sshbuf._offset;
    163 	}
    164 
    165 	return (new Certificate(cert));
    166 }
    167 
    168 function int64ToDate(buf) {
    169 	var i = buf.readUInt32BE(0) * 4294967296;
    170 	i += buf.readUInt32BE(4);
    171 	var d = new Date();
    172 	d.setTime(i * 1000);
    173 	d.sourceInt64 = buf;
    174 	return (d);
    175 }
    176 
    177 function dateToInt64(date) {
    178 	if (date.sourceInt64 !== undefined)
    179 		return (date.sourceInt64);
    180 	var i = Math.round(date.getTime() / 1000);
    181 	var upper = Math.floor(i / 4294967296);
    182 	var lower = Math.floor(i % 4294967296);
    183 	var buf = Buffer.alloc(8);
    184 	buf.writeUInt32BE(upper, 0);
    185 	buf.writeUInt32BE(lower, 4);
    186 	return (buf);
    187 }
    188 
    189 function sign(cert, key) {
    190 	if (cert.signatures.openssh === undefined)
    191 		cert.signatures.openssh = {};
    192 	try {
    193 		var blob = toBuffer(cert, true);
    194 	} catch (e) {
    195 		delete (cert.signatures.openssh);
    196 		return (false);
    197 	}
    198 	var sig = cert.signatures.openssh;
    199 	var hashAlgo = undefined;
    200 	if (key.type === 'rsa' || key.type === 'dsa')
    201 		hashAlgo = 'sha1';
    202 	var signer = key.createSign(hashAlgo);
    203 	signer.write(blob);
    204 	sig.signature = signer.sign();
    205 	return (true);
    206 }
    207 
    208 function signAsync(cert, signer, done) {
    209 	if (cert.signatures.openssh === undefined)
    210 		cert.signatures.openssh = {};
    211 	try {
    212 		var blob = toBuffer(cert, true);
    213 	} catch (e) {
    214 		delete (cert.signatures.openssh);
    215 		done(e);
    216 		return;
    217 	}
    218 	var sig = cert.signatures.openssh;
    219 
    220 	signer(blob, function (err, signature) {
    221 		if (err) {
    222 			done(err);
    223 			return;
    224 		}
    225 		try {
    226 			/*
    227 			 * This will throw if the signature isn't of a
    228 			 * type/algo that can be used for SSH.
    229 			 */
    230 			signature.toBuffer('ssh');
    231 		} catch (e) {
    232 			done(e);
    233 			return;
    234 		}
    235 		sig.signature = signature;
    236 		done();
    237 	});
    238 }
    239 
    240 function write(cert, options) {
    241 	if (options === undefined)
    242 		options = {};
    243 
    244 	var blob = toBuffer(cert);
    245 	var out = getCertType(cert.subjectKey) + ' ' + blob.toString('base64');
    246 	if (options.comment)
    247 		out = out + ' ' + options.comment;
    248 	return (out);
    249 }
    250 
    251 
    252 function toBuffer(cert, noSig) {
    253 	assert.object(cert.signatures.openssh, 'signature for openssh format');
    254 	var sig = cert.signatures.openssh;
    255 
    256 	if (sig.nonce === undefined)
    257 		sig.nonce = crypto.randomBytes(16);
    258 	var buf = new SSHBuffer({});
    259 	buf.writeString(getCertType(cert.subjectKey));
    260 	buf.writeBuffer(sig.nonce);
    261 
    262 	var key = cert.subjectKey;
    263 	var algInfo = algs.info[key.type];
    264 	algInfo.parts.forEach(function (part) {
    265 		buf.writePart(key.part[part]);
    266 	});
    267 
    268 	buf.writeInt64(cert.serial);
    269 
    270 	var type = cert.subjects[0].type;
    271 	assert.notStrictEqual(type, 'unknown');
    272 	cert.subjects.forEach(function (id) {
    273 		assert.strictEqual(id.type, type);
    274 	});
    275 	type = TYPES[type];
    276 	buf.writeInt(type);
    277 
    278 	if (sig.keyId === undefined) {
    279 		sig.keyId = cert.subjects[0].type + '_' +
    280 		    (cert.subjects[0].uid || cert.subjects[0].hostname);
    281 	}
    282 	buf.writeString(sig.keyId);
    283 
    284 	var sub = new SSHBuffer({});
    285 	cert.subjects.forEach(function (id) {
    286 		if (type === TYPES.host)
    287 			sub.writeString(id.hostname);
    288 		else if (type === TYPES.user)
    289 			sub.writeString(id.uid);
    290 	});
    291 	buf.writeBuffer(sub.toBuffer());
    292 
    293 	buf.writeInt64(dateToInt64(cert.validFrom));
    294 	buf.writeInt64(dateToInt64(cert.validUntil));
    295 
    296 	var exts = sig.exts;
    297 	if (exts === undefined)
    298 		exts = [];
    299 
    300 	var extbuf = new SSHBuffer({});
    301 	exts.forEach(function (ext) {
    302 		if (ext.critical !== true)
    303 			return;
    304 		extbuf.writeString(ext.name);
    305 		extbuf.writeBuffer(ext.data);
    306 	});
    307 	buf.writeBuffer(extbuf.toBuffer());
    308 
    309 	extbuf = new SSHBuffer({});
    310 	exts.forEach(function (ext) {
    311 		if (ext.critical === true)
    312 			return;
    313 		extbuf.writeString(ext.name);
    314 		extbuf.writeBuffer(ext.data);
    315 	});
    316 	buf.writeBuffer(extbuf.toBuffer());
    317 
    318 	/* reserved */
    319 	buf.writeBuffer(Buffer.alloc(0));
    320 
    321 	sub = rfc4253.write(cert.issuerKey);
    322 	buf.writeBuffer(sub);
    323 
    324 	if (!noSig)
    325 		buf.writeBuffer(sig.signature.toBuffer('ssh'));
    326 
    327 	return (buf.toBuffer());
    328 }
    329 
    330 function getAlg(certType) {
    331 	if (certType === 'ssh-rsa-cert-v01@openssh.com')
    332 		return ('rsa');
    333 	if (certType === 'ssh-dss-cert-v01@openssh.com')
    334 		return ('dsa');
    335 	if (certType.match(ECDSA_ALGO))
    336 		return ('ecdsa');
    337 	if (certType === 'ssh-ed25519-cert-v01@openssh.com')
    338 		return ('ed25519');
    339 	throw (new Error('Unsupported cert type ' + certType));
    340 }
    341 
    342 function getCertType(key) {
    343 	if (key.type === 'rsa')
    344 		return ('ssh-rsa-cert-v01@openssh.com');
    345 	if (key.type === 'dsa')
    346 		return ('ssh-dss-cert-v01@openssh.com');
    347 	if (key.type === 'ecdsa')
    348 		return ('ecdsa-sha2-' + key.curve + '-cert-v01@openssh.com');
    349 	if (key.type === 'ed25519')
    350 		return ('ssh-ed25519-cert-v01@openssh.com');
    351 	throw (new Error('Unsupported key type ' + key.type));
    352 }