From 846b89c5bf07100f9bf1e46bcac55acab5535e26 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Wed, 23 Dec 2020 15:31:58 +0100 Subject: [PATCH] luci-mod-system: fix parsing SSH pubkeys with options Also eliminate some duplicate code while we're at it. Fixes: #4684 Signed-off-by: Jo-Philipp Wich --- .../resources/view/system/sshkeys.js | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js index 4740b8c9dd..1e38a620fa 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js @@ -22,12 +22,13 @@ var SSHPubkeyDecoder = baseclass.singleton({ decode: function(s) { - var parts = s.split(/\s+/); - if (parts.length < 2) + var parts = s.trim().match(/^((?:(?:^|,)[^ =,]+(?:=(?:[^ ",]+|"(?:[^"\\]|\\.)*"))?)+ +)?(ssh-dss|ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp[0-9]+) +([^ ]+)( +.*)?$/); + + if (!parts) return null; var key = null; - try { key = atob(parts[1]); } catch(e) {} + try { key = atob(parts[3]); } catch(e) {} if (!key) return null; @@ -40,7 +41,7 @@ var SSHPubkeyDecoder = baseclass.singleton({ return null; var type = key.substr(off + 4, len); - if (type !== parts[0]) + if (type !== parts[2]) return null; off += 4 + len; @@ -72,22 +73,32 @@ var SSHPubkeyDecoder = baseclass.singleton({ if (len2 & 1) len2--; - var comment = parts.slice(2).join(' '), - fprint = parts[1].length > 68 ? parts[1].substr(0, 33) + '…' + parts[1].substr(-34) : parts[1]; + var comment = (parts[4] || '').trim(), + fprint = parts[3].length > 68 ? parts[3].substr(0, 33) + '…' + parts[3].substr(-34) : parts[3]; + + var options = null; + (parts[1] || '').trim().replace(/(?:^|,)([^ =,]+)(?:=(?:([^ ",]+)|"((?:[^"\\]|\\.)*)"))?/g, function(m, k, p, q) { + options = options || {}; + + if (options.hasOwnProperty(k)) + options[k] += ',' + (q || p || true); + else + options[k] = (q || p || true); + }); switch (type) { case 'ssh-rsa': - return { type: 'RSA', bits: len2 * 8, comment: comment, fprint: fprint }; + return { type: 'RSA', bits: len2 * 8, comment: comment, options: options, fprint: fprint, src: s }; case 'ssh-dss': - return { type: 'DSA', bits: len1 * 8, comment: comment, fprint: fprint }; + return { type: 'DSA', bits: len1 * 8, comment: comment, options: options, fprint: fprint, src: s }; case 'ssh-ed25519': - return { type: 'ECDH', curve: 'Curve25519', comment: comment, fprint: fprint }; + return { type: 'ECDH', curve: 'Curve25519', comment: comment, options: options, fprint: fprint, src: s }; case 'ecdsa-sha2': - return { type: 'ECDSA', curve: curve, comment: comment, fprint: fprint }; + return { type: 'ECDSA', curve: curve, comment: comment, options: options, fprint: fprint, src: s }; default: return null; @@ -95,6 +106,24 @@ var SSHPubkeyDecoder = baseclass.singleton({ } }); +function renderKeyItem(pubkey) { + return E('div', { + class: 'item', + click: isReadonlyView ? null : removeKey, + 'data-key': pubkey.src + }, [ + E('strong', pubkey.comment || _('Unnamed key')), E('br'), + E('small', [ + '%s, %s'.format(pubkey.type, pubkey.curve || _('%d Bit').format(pubkey.bits)), + pubkey.options ? E([], [ + ' / ', _('Options:'), ' ', + E('code', Object.keys(pubkey.options).sort().join(', ')) + ]) : '', + E('br'), E('code', pubkey.fprint) + ]) + ]); +} + function renderKeys(keys) { var list = document.querySelector('.cbi-dynlist'); @@ -104,17 +133,7 @@ function renderKeys(keys) { keys.forEach(function(key) { var pubkey = SSHPubkeyDecoder.decode(key); if (pubkey) - list.insertBefore(E('div', { - class: 'item', - click: removeKey, - 'data-key': key - }, [ - E('strong', pubkey.comment || _('Unnamed key')), E('br'), - E('small', [ - '%s, %s'.format(pubkey.type, pubkey.curve || _('%d Bit').format(pubkey.bits)), - E('br'), E('code', pubkey.fprint) - ]) - ]), list.lastElementChild); + list.insertBefore(renderKeyItem(pubkey), list.lastElementChild); }); if (list.firstElementChild === list.lastElementChild) @@ -159,7 +178,7 @@ function addKey(ev) { input.value = ''; return saveKeys(keys).then(function() { - var added = list.querySelector('[data-key="%s"]'.format(key)); + var added = list.querySelector('[data-key="%s"]'.format(key.replace(/["\\]/g, '\\$&'))); if (added) added.classList.add('flash'); }); @@ -220,8 +239,10 @@ function handleWindowDragDropIgnore(ev) { return view.extend({ load: function() { return fs.lines('/etc/dropbear/authorized_keys').then(function(lines) { - return lines.filter(function(line) { - return line.match(/^(ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2)\b/) != null; + return lines.map(function(line) { + return SSHPubkeyDecoder.decode(line); + }).filter(function(line) { + return line != null; }); }); }, @@ -248,20 +269,8 @@ return view.extend({ ]) ]); - keys.forEach(L.bind(function(key) { - var pubkey = SSHPubkeyDecoder.decode(key); - if (pubkey) - list.insertBefore(E('div', { - class: 'item', - click: isReadonlyView ? null : ui.createHandlerFn(this, removeKey), - 'data-key': key - }, [ - E('strong', pubkey.comment || _('Unnamed key')), E('br'), - E('small', [ - '%s, %s'.format(pubkey.type, pubkey.curve || _('%d Bit').format(pubkey.bits)), - E('br'), E('code', pubkey.fprint) - ]) - ]), list.lastElementChild); + keys.forEach(L.bind(function(pubkey) { + list.insertBefore(renderKeyItem(pubkey), list.lastElementChild); }, this)); if (list.firstElementChild === list.lastElementChild) -- 2.30.2