| // 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)); | 
| } |