1 // Copyright 2016 Joyent, Inc.
9 /* Internal private API */
10 fromBuffer: fromBuffer,
14 var assert = require('assert-plus');
15 var SSHBuffer = require('../ssh-buffer');
16 var crypto = require('crypto');
17 var algs = require('../algs');
18 var Key = require('../key');
19 var PrivateKey = require('../private-key');
20 var Identity = require('../identity');
21 var rfc4253 = require('./rfc4253');
22 var Signature = require('../signature');
23 var utils = require('../utils');
24 var Certificate = require('../certificate');
26 function verify(cert, key) {
28 * We always give an issuerKey, so if our verify() is being called then
29 * there was no signature. Return false.
38 Object.keys(TYPES).forEach(function (k) { TYPES[TYPES[k]] = k; });
40 var ECDSA_ALGO = /^ecdsa-sha2-([^@-]+)-cert-v01@openssh.com$/;
42 function read(buf, options) {
43 if (Buffer.isBuffer(buf))
44 buf = buf.toString('ascii');
45 var parts = buf.trim().split(/[ \t\n]+/g);
46 if (parts.length < 2 || parts.length > 3)
47 throw (new Error('Not a valid SSH certificate line'));
52 data = new Buffer(data, 'base64');
53 return (fromBuffer(data, algo));
56 function fromBuffer(data, algo, partial) {
57 var sshbuf = new SSHBuffer({ buffer: data });
58 var innerAlgo = sshbuf.readString();
59 if (algo !== undefined && innerAlgo !== algo)
60 throw (new Error('SSH certificate algorithm mismatch'));
61 if (algo === undefined)
66 cert.signatures.openssh = {};
68 cert.signatures.openssh.nonce = sshbuf.readBuffer();
71 var parts = (key.parts = []);
72 key.type = getAlg(algo);
74 var partCount = algs.info[key.type].parts.length;
75 while (parts.length < partCount)
76 parts.push(sshbuf.readPart());
77 assert.ok(parts.length >= 1, 'key must have at least one part');
79 var algInfo = algs.info[key.type];
80 if (key.type === 'ecdsa') {
81 var res = ECDSA_ALGO.exec(algo);
82 assert.ok(res !== null);
83 assert.strictEqual(res[1], parts[0].data.toString());
86 for (var i = 0; i < algInfo.parts.length; ++i) {
87 parts[i].name = algInfo.parts[i];
88 if (parts[i].name !== 'curve' &&
89 algInfo.normalize !== false) {
91 p.data = utils.mpNormalize(p.data);
95 cert.subjectKey = new Key(key);
97 cert.serial = sshbuf.readInt64();
99 var type = TYPES[sshbuf.readInt()];
100 assert.string(type, 'valid cert type');
102 cert.signatures.openssh.keyId = sshbuf.readString();
105 var pbuf = sshbuf.readBuffer();
106 var psshbuf = new SSHBuffer({ buffer: pbuf });
107 while (!psshbuf.atEnd())
108 principals.push(psshbuf.readString());
109 if (principals.length === 0)
112 cert.subjects = principals.map(function (pr) {
114 return (Identity.forUser(pr));
115 else if (type === 'host')
116 return (Identity.forHost(pr));
117 throw (new Error('Unknown identity type ' + type));
120 cert.validFrom = int64ToDate(sshbuf.readInt64());
121 cert.validUntil = int64ToDate(sshbuf.readInt64());
123 cert.signatures.openssh.critical = sshbuf.readBuffer();
124 cert.signatures.openssh.exts = sshbuf.readBuffer();
129 var signingKeyBuf = sshbuf.readBuffer();
130 cert.issuerKey = rfc4253.read(signingKeyBuf);
133 * OpenSSH certs don't give the identity of the issuer, just their
134 * public key. So, we use an Identity that matches anything. The
135 * isSignedBy() function will later tell you if the key matches.
137 cert.issuer = Identity.forHost('**');
139 var sigBuf = sshbuf.readBuffer();
140 cert.signatures.openssh.signature =
141 Signature.parse(sigBuf, cert.issuerKey.type, 'ssh');
143 if (partial !== undefined) {
144 partial.remainder = sshbuf.remainder();
145 partial.consumed = sshbuf._offset;
148 return (new Certificate(cert));
151 function int64ToDate(buf) {
152 var i = buf.readUInt32BE(0) * 4294967296;
153 i += buf.readUInt32BE(4);
160 function dateToInt64(date) {
161 if (date.sourceInt64 !== undefined)
162 return (date.sourceInt64);
163 var i = Math.round(date.getTime() / 1000);
164 var upper = Math.floor(i / 4294967296);
165 var lower = Math.floor(i % 4294967296);
166 var buf = new Buffer(8);
167 buf.writeUInt32BE(upper, 0);
168 buf.writeUInt32BE(lower, 4);
172 function sign(cert, key) {
173 if (cert.signatures.openssh === undefined)
174 cert.signatures.openssh = {};
176 var blob = toBuffer(cert, true);
178 delete (cert.signatures.openssh);
181 var sig = cert.signatures.openssh;
182 var hashAlgo = undefined;
183 if (key.type === 'rsa' || key.type === 'dsa')
185 var signer = key.createSign(hashAlgo);
187 sig.signature = signer.sign();
191 function write(cert, options) {
192 if (options === undefined)
195 var blob = toBuffer(cert);
196 var out = getCertType(cert.subjectKey) + ' ' + blob.toString('base64');
198 out = out + ' ' + options.comment;
203 function toBuffer(cert, noSig) {
204 assert.object(cert.signatures.openssh, 'signature for openssh format');
205 var sig = cert.signatures.openssh;
207 if (sig.nonce === undefined)
208 sig.nonce = crypto.randomBytes(16);
209 var buf = new SSHBuffer({});
210 buf.writeString(getCertType(cert.subjectKey));
211 buf.writeBuffer(sig.nonce);
213 var key = cert.subjectKey;
214 var algInfo = algs.info[key.type];
215 algInfo.parts.forEach(function (part) {
216 buf.writePart(key.part[part]);
219 buf.writeInt64(cert.serial);
221 var type = cert.subjects[0].type;
222 assert.notStrictEqual(type, 'unknown');
223 cert.subjects.forEach(function (id) {
224 assert.strictEqual(id.type, type);
229 if (sig.keyId === undefined) {
230 sig.keyId = cert.subjects[0].type + '_' +
231 (cert.subjects[0].uid || cert.subjects[0].hostname);
233 buf.writeString(sig.keyId);
235 var sub = new SSHBuffer({});
236 cert.subjects.forEach(function (id) {
237 if (type === TYPES.host)
238 sub.writeString(id.hostname);
239 else if (type === TYPES.user)
240 sub.writeString(id.uid);
242 buf.writeBuffer(sub.toBuffer());
244 buf.writeInt64(dateToInt64(cert.validFrom));
245 buf.writeInt64(dateToInt64(cert.validUntil));
247 if (sig.critical === undefined)
248 sig.critical = new Buffer(0);
249 buf.writeBuffer(sig.critical);
251 if (sig.exts === undefined)
252 sig.exts = new Buffer(0);
253 buf.writeBuffer(sig.exts);
256 buf.writeBuffer(new Buffer(0));
258 sub = rfc4253.write(cert.issuerKey);
259 buf.writeBuffer(sub);
262 buf.writeBuffer(sig.signature.toBuffer('ssh'));
264 return (buf.toBuffer());
267 function getAlg(certType) {
268 if (certType === 'ssh-rsa-cert-v01@openssh.com')
270 if (certType === 'ssh-dss-cert-v01@openssh.com')
272 if (certType.match(ECDSA_ALGO))
274 if (certType === 'ssh-ed25519-cert-v01@openssh.com')
276 throw (new Error('Unsupported cert type ' + certType));
279 function getCertType(key) {
280 if (key.type === 'rsa')
281 return ('ssh-rsa-cert-v01@openssh.com');
282 if (key.type === 'dsa')
283 return ('ssh-dss-cert-v01@openssh.com');
284 if (key.type === 'ecdsa')
285 return ('ecdsa-sha2-' + key.curve + '-cert-v01@openssh.com');
286 if (key.type === 'ed25519')
287 return ('ssh-ed25519-cert-v01@openssh.com');
288 throw (new Error('Unsupported key type ' + key.type));