mirror of
				https://github.com/1disk/edp445.git
				synced 2024-08-14 22:47:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			352 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Copyright 2017 Joyent, Inc.
 | |
| 
 | |
| module.exports = {
 | |
| 	read: read,
 | |
| 	verify: verify,
 | |
| 	sign: sign,
 | |
| 	signAsync: signAsync,
 | |
| 	write: write,
 | |
| 
 | |
| 	/* Internal private API */
 | |
| 	fromBuffer: fromBuffer,
 | |
| 	toBuffer: toBuffer
 | |
| };
 | |
| 
 | |
| var assert = require('assert-plus');
 | |
| var SSHBuffer = require('../ssh-buffer');
 | |
| var crypto = require('crypto');
 | |
| var Buffer = require('safer-buffer').Buffer;
 | |
| var algs = require('../algs');
 | |
| var Key = require('../key');
 | |
| var PrivateKey = require('../private-key');
 | |
| var Identity = require('../identity');
 | |
| var rfc4253 = require('./rfc4253');
 | |
| var Signature = require('../signature');
 | |
| var utils = require('../utils');
 | |
| var Certificate = require('../certificate');
 | |
| 
 | |
| function verify(cert, key) {
 | |
| 	/*
 | |
| 	 * We always give an issuerKey, so if our verify() is being called then
 | |
| 	 * there was no signature. Return false.
 | |
| 	 */
 | |
| 	return (false);
 | |
| }
 | |
| 
 | |
| var TYPES = {
 | |
| 	'user': 1,
 | |
| 	'host': 2
 | |
| };
 | |
| Object.keys(TYPES).forEach(function (k) { TYPES[TYPES[k]] = k; });
 | |
| 
 | |
| var ECDSA_ALGO = /^ecdsa-sha2-([^@-]+)-cert-v01@openssh.com$/;
 | |
| 
 | |
| function read(buf, options) {
 | |
| 	if (Buffer.isBuffer(buf))
 | |
| 		buf = buf.toString('ascii');
 | |
| 	var parts = buf.trim().split(/[ \t\n]+/g);
 | |
| 	if (parts.length < 2 || parts.length > 3)
 | |
| 		throw (new Error('Not a valid SSH certificate line'));
 | |
| 
 | |
| 	var algo = parts[0];
 | |
| 	var data = parts[1];
 | |
| 
 | |
| 	data = Buffer.from(data, 'base64');
 | |
| 	return (fromBuffer(data, algo));
 | |
| }
 | |
| 
 | |
| function fromBuffer(data, algo, partial) {
 | |
| 	var sshbuf = new SSHBuffer({ buffer: data });
 | |
| 	var innerAlgo = sshbuf.readString();
 | |
| 	if (algo !== undefined && innerAlgo !== algo)
 | |
| 		throw (new Error('SSH certificate algorithm mismatch'));
 | |
| 	if (algo === undefined)
 | |
| 		algo = innerAlgo;
 | |
| 
 | |
| 	var cert = {};
 | |
| 	cert.signatures = {};
 | |
| 	cert.signatures.openssh = {};
 | |
| 
 | |
| 	cert.signatures.openssh.nonce = sshbuf.readBuffer();
 | |
| 
 | |
| 	var key = {};
 | |
| 	var parts = (key.parts = []);
 | |
| 	key.type = getAlg(algo);
 | |
| 
 | |
| 	var partCount = algs.info[key.type].parts.length;
 | |
| 	while (parts.length < partCount)
 | |
| 		parts.push(sshbuf.readPart());
 | |
| 	assert.ok(parts.length >= 1, 'key must have at least one part');
 | |
| 
 | |
| 	var algInfo = algs.info[key.type];
 | |
| 	if (key.type === 'ecdsa') {
 | |
| 		var res = ECDSA_ALGO.exec(algo);
 | |
| 		assert.ok(res !== null);
 | |
| 		assert.strictEqual(res[1], parts[0].data.toString());
 | |
| 	}
 | |
| 
 | |
| 	for (var i = 0; i < algInfo.parts.length; ++i) {
 | |
| 		parts[i].name = algInfo.parts[i];
 | |
| 		if (parts[i].name !== 'curve' &&
 | |
| 		    algInfo.normalize !== false) {
 | |
| 			var p = parts[i];
 | |
| 			p.data = utils.mpNormalize(p.data);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	cert.subjectKey = new Key(key);
 | |
| 
 | |
| 	cert.serial = sshbuf.readInt64();
 | |
| 
 | |
| 	var type = TYPES[sshbuf.readInt()];
 | |
| 	assert.string(type, 'valid cert type');
 | |
| 
 | |
| 	cert.signatures.openssh.keyId = sshbuf.readString();
 | |
| 
 | |
| 	var principals = [];
 | |
| 	var pbuf = sshbuf.readBuffer();
 | |
| 	var psshbuf = new SSHBuffer({ buffer: pbuf });
 | |
| 	while (!psshbuf.atEnd())
 | |
| 		principals.push(psshbuf.readString());
 | |
| 	if (principals.length === 0)
 | |
| 		principals = ['*'];
 | |
| 
 | |
| 	cert.subjects = principals.map(function (pr) {
 | |
| 		if (type === 'user')
 | |
| 			return (Identity.forUser(pr));
 | |
| 		else if (type === 'host')
 | |
| 			return (Identity.forHost(pr));
 | |
| 		throw (new Error('Unknown identity type ' + type));
 | |
| 	});
 | |
| 
 | |
| 	cert.validFrom = int64ToDate(sshbuf.readInt64());
 | |
| 	cert.validUntil = int64ToDate(sshbuf.readInt64());
 | |
| 
 | |
| 	var exts = [];
 | |
| 	var extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
 | |
| 	var ext;
 | |
| 	while (!extbuf.atEnd()) {
 | |
| 		ext = { critical: true };
 | |
| 		ext.name = extbuf.readString();
 | |
| 		ext.data = extbuf.readBuffer();
 | |
| 		exts.push(ext);
 | |
| 	}
 | |
| 	extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
 | |
| 	while (!extbuf.atEnd()) {
 | |
| 		ext = { critical: false };
 | |
| 		ext.name = extbuf.readString();
 | |
| 		ext.data = extbuf.readBuffer();
 | |
| 		exts.push(ext);
 | |
| 	}
 | |
| 	cert.signatures.openssh.exts = exts;
 | |
| 
 | |
| 	/* reserved */
 | |
| 	sshbuf.readBuffer();
 | |
| 
 | |
| 	var signingKeyBuf = sshbuf.readBuffer();
 | |
| 	cert.issuerKey = rfc4253.read(signingKeyBuf);
 | |
| 
 | |
| 	/*
 | |
| 	 * OpenSSH certs don't give the identity of the issuer, just their
 | |
| 	 * public key. So, we use an Identity that matches anything. The
 | |
| 	 * isSignedBy() function will later tell you if the key matches.
 | |
| 	 */
 | |
| 	cert.issuer = Identity.forHost('**');
 | |
| 
 | |
| 	var sigBuf = sshbuf.readBuffer();
 | |
| 	cert.signatures.openssh.signature =
 | |
| 	    Signature.parse(sigBuf, cert.issuerKey.type, 'ssh');
 | |
| 
 | |
| 	if (partial !== undefined) {
 | |
| 		partial.remainder = sshbuf.remainder();
 | |
| 		partial.consumed = sshbuf._offset;
 | |
| 	}
 | |
| 
 | |
| 	return (new Certificate(cert));
 | |
| }
 | |
| 
 | |
| function int64ToDate(buf) {
 | |
| 	var i = buf.readUInt32BE(0) * 4294967296;
 | |
| 	i += buf.readUInt32BE(4);
 | |
| 	var d = new Date();
 | |
| 	d.setTime(i * 1000);
 | |
| 	d.sourceInt64 = buf;
 | |
| 	return (d);
 | |
| }
 | |
| 
 | |
| function dateToInt64(date) {
 | |
| 	if (date.sourceInt64 !== undefined)
 | |
| 		return (date.sourceInt64);
 | |
| 	var i = Math.round(date.getTime() / 1000);
 | |
| 	var upper = Math.floor(i / 4294967296);
 | |
| 	var lower = Math.floor(i % 4294967296);
 | |
| 	var buf = Buffer.alloc(8);
 | |
| 	buf.writeUInt32BE(upper, 0);
 | |
| 	buf.writeUInt32BE(lower, 4);
 | |
| 	return (buf);
 | |
| }
 | |
| 
 | |
| function sign(cert, key) {
 | |
| 	if (cert.signatures.openssh === undefined)
 | |
| 		cert.signatures.openssh = {};
 | |
| 	try {
 | |
| 		var blob = toBuffer(cert, true);
 | |
| 	} catch (e) {
 | |
| 		delete (cert.signatures.openssh);
 | |
| 		return (false);
 | |
| 	}
 | |
| 	var sig = cert.signatures.openssh;
 | |
| 	var hashAlgo = undefined;
 | |
| 	if (key.type === 'rsa' || key.type === 'dsa')
 | |
| 		hashAlgo = 'sha1';
 | |
| 	var signer = key.createSign(hashAlgo);
 | |
| 	signer.write(blob);
 | |
| 	sig.signature = signer.sign();
 | |
| 	return (true);
 | |
| }
 | |
| 
 | |
| function signAsync(cert, signer, done) {
 | |
| 	if (cert.signatures.openssh === undefined)
 | |
| 		cert.signatures.openssh = {};
 | |
| 	try {
 | |
| 		var blob = toBuffer(cert, true);
 | |
| 	} catch (e) {
 | |
| 		delete (cert.signatures.openssh);
 | |
| 		done(e);
 | |
| 		return;
 | |
| 	}
 | |
| 	var sig = cert.signatures.openssh;
 | |
| 
 | |
| 	signer(blob, function (err, signature) {
 | |
| 		if (err) {
 | |
| 			done(err);
 | |
| 			return;
 | |
| 		}
 | |
| 		try {
 | |
| 			/*
 | |
| 			 * This will throw if the signature isn't of a
 | |
| 			 * type/algo that can be used for SSH.
 | |
| 			 */
 | |
| 			signature.toBuffer('ssh');
 | |
| 		} catch (e) {
 | |
| 			done(e);
 | |
| 			return;
 | |
| 		}
 | |
| 		sig.signature = signature;
 | |
| 		done();
 | |
| 	});
 | |
| }
 | |
| 
 | |
| function write(cert, options) {
 | |
| 	if (options === undefined)
 | |
| 		options = {};
 | |
| 
 | |
| 	var blob = toBuffer(cert);
 | |
| 	var out = getCertType(cert.subjectKey) + ' ' + blob.toString('base64');
 | |
| 	if (options.comment)
 | |
| 		out = out + ' ' + options.comment;
 | |
| 	return (out);
 | |
| }
 | |
| 
 | |
| 
 | |
| function toBuffer(cert, noSig) {
 | |
| 	assert.object(cert.signatures.openssh, 'signature for openssh format');
 | |
| 	var sig = cert.signatures.openssh;
 | |
| 
 | |
| 	if (sig.nonce === undefined)
 | |
| 		sig.nonce = crypto.randomBytes(16);
 | |
| 	var buf = new SSHBuffer({});
 | |
| 	buf.writeString(getCertType(cert.subjectKey));
 | |
| 	buf.writeBuffer(sig.nonce);
 | |
| 
 | |
| 	var key = cert.subjectKey;
 | |
| 	var algInfo = algs.info[key.type];
 | |
| 	algInfo.parts.forEach(function (part) {
 | |
| 		buf.writePart(key.part[part]);
 | |
| 	});
 | |
| 
 | |
| 	buf.writeInt64(cert.serial);
 | |
| 
 | |
| 	var type = cert.subjects[0].type;
 | |
| 	assert.notStrictEqual(type, 'unknown');
 | |
| 	cert.subjects.forEach(function (id) {
 | |
| 		assert.strictEqual(id.type, type);
 | |
| 	});
 | |
| 	type = TYPES[type];
 | |
| 	buf.writeInt(type);
 | |
| 
 | |
| 	if (sig.keyId === undefined) {
 | |
| 		sig.keyId = cert.subjects[0].type + '_' +
 | |
| 		    (cert.subjects[0].uid || cert.subjects[0].hostname);
 | |
| 	}
 | |
| 	buf.writeString(sig.keyId);
 | |
| 
 | |
| 	var sub = new SSHBuffer({});
 | |
| 	cert.subjects.forEach(function (id) {
 | |
| 		if (type === TYPES.host)
 | |
| 			sub.writeString(id.hostname);
 | |
| 		else if (type === TYPES.user)
 | |
| 			sub.writeString(id.uid);
 | |
| 	});
 | |
| 	buf.writeBuffer(sub.toBuffer());
 | |
| 
 | |
| 	buf.writeInt64(dateToInt64(cert.validFrom));
 | |
| 	buf.writeInt64(dateToInt64(cert.validUntil));
 | |
| 
 | |
| 	var exts = sig.exts;
 | |
| 	if (exts === undefined)
 | |
| 		exts = [];
 | |
| 
 | |
| 	var extbuf = new SSHBuffer({});
 | |
| 	exts.forEach(function (ext) {
 | |
| 		if (ext.critical !== true)
 | |
| 			return;
 | |
| 		extbuf.writeString(ext.name);
 | |
| 		extbuf.writeBuffer(ext.data);
 | |
| 	});
 | |
| 	buf.writeBuffer(extbuf.toBuffer());
 | |
| 
 | |
| 	extbuf = new SSHBuffer({});
 | |
| 	exts.forEach(function (ext) {
 | |
| 		if (ext.critical === true)
 | |
| 			return;
 | |
| 		extbuf.writeString(ext.name);
 | |
| 		extbuf.writeBuffer(ext.data);
 | |
| 	});
 | |
| 	buf.writeBuffer(extbuf.toBuffer());
 | |
| 
 | |
| 	/* reserved */
 | |
| 	buf.writeBuffer(Buffer.alloc(0));
 | |
| 
 | |
| 	sub = rfc4253.write(cert.issuerKey);
 | |
| 	buf.writeBuffer(sub);
 | |
| 
 | |
| 	if (!noSig)
 | |
| 		buf.writeBuffer(sig.signature.toBuffer('ssh'));
 | |
| 
 | |
| 	return (buf.toBuffer());
 | |
| }
 | |
| 
 | |
| function getAlg(certType) {
 | |
| 	if (certType === 'ssh-rsa-cert-v01@openssh.com')
 | |
| 		return ('rsa');
 | |
| 	if (certType === 'ssh-dss-cert-v01@openssh.com')
 | |
| 		return ('dsa');
 | |
| 	if (certType.match(ECDSA_ALGO))
 | |
| 		return ('ecdsa');
 | |
| 	if (certType === 'ssh-ed25519-cert-v01@openssh.com')
 | |
| 		return ('ed25519');
 | |
| 	throw (new Error('Unsupported cert type ' + certType));
 | |
| }
 | |
| 
 | |
| function getCertType(key) {
 | |
| 	if (key.type === 'rsa')
 | |
| 		return ('ssh-rsa-cert-v01@openssh.com');
 | |
| 	if (key.type === 'dsa')
 | |
| 		return ('ssh-dss-cert-v01@openssh.com');
 | |
| 	if (key.type === 'ecdsa')
 | |
| 		return ('ecdsa-sha2-' + key.curve + '-cert-v01@openssh.com');
 | |
| 	if (key.type === 'ed25519')
 | |
| 		return ('ssh-ed25519-cert-v01@openssh.com');
 | |
| 	throw (new Error('Unsupported key type ' + key.type));
 | |
| }
 |