You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							353 lines
						
					
					
						
							8.5 KiB
						
					
					
				
			
		
		
	
	
							353 lines
						
					
					
						
							8.5 KiB
						
					
					
				// 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));
 | 
						|
}
 |