3 <style type="text/css">
9 <script type="application/javascript">//<![CDATA[
10 SSHPubkeyDecoder.prototype = {
11 lengthDecode: function(s, off)
13 var l = (s.charCodeAt(off++) << 24) |
14 (s.charCodeAt(off++) << 16) |
15 (s.charCodeAt(off++) << 8) |
18 if (l < 0 || (off + l) > s.length)
26 var parts = s.split(/\s+/);
31 try { key = atob(parts[1]); } catch(e) {}
38 len = this.lengthDecode(key, off);
43 var type = key.substr(off + 4, len);
44 if (type !== parts[0])
49 var len1 = off < key.length ? this.lengthDecode(key, off) : 0;
54 if (type.indexOf('ecdsa-sha2-') === 0) {
55 curve = key.substr(off + 4, len1);
57 if (!len1 || type.substr(11) !== curve)
61 curve = curve.replace(/^nistp(\d+)$/, 'NIST P-$1');
66 var len2 = off < key.length ? this.lengthDecode(key, off) : 0;
76 var comment = parts.slice(2).join(' '),
77 fprint = parts[1].length > 68 ? parts[1].substr(0, 33) + '…' + parts[1].substr(-34) : parts[1];
82 return { type: 'RSA', bits: len2 * 8, comment: comment, fprint: fprint };
85 return { type: 'DSA', bits: len1 * 8, comment: comment, fprint: fprint };
88 return { type: 'ECDH', curve: 'Curve25519', comment: comment, fprint: fprint };
91 return { type: 'ECDSA', curve: curve, comment: comment, fprint: fprint };
99 function SSHPubkeyDecoder() {}
101 function renderKeys(keys) {
102 var list = document.querySelector('.cbi-dynlist[name="sshkeys"]'),
103 decoder = new SSHPubkeyDecoder();
105 while (!matchesElem(list.firstElementChild, '.add-item'))
106 list.removeChild(list.firstElementChild);
108 keys.forEach(function(key) {
109 var pubkey = decoder.decode(key);
111 list.insertBefore(E('div', {
116 E('strong', pubkey.comment || _('Unnamed key')), E('br'),
118 '%s, %s'.format(pubkey.type, pubkey.curve || _('%d Bit').format(pubkey.bits)),
119 E('br'), E('code', pubkey.fprint)
121 ]), list.lastElementChild);
124 if (list.firstElementChild === list.lastElementChild)
125 list.insertBefore(E('p', _('No public keys present yet.')), list.lastElementChild);
128 function saveKeys(keys) {
129 showModal('<%:Add key%>', E('div', { class: 'spinning' }, _('Saving keys…')));
130 (new XHR()).post('<%=url("admin/system/admin/sshkeys/json")%>', { token: '<%=token%>', keys: JSON.stringify(keys) }, function(xhr, keys) {
136 function addKey(ev) {
137 var decoder = new SSHPubkeyDecoder(),
138 list = findParent(ev.target, '.cbi-dynlist'),
139 input = list.querySelector('input[type="text"]'),
140 key = input.value.trim(),
141 pubkey = decoder.decode(key),
147 list.querySelectorAll('.item').forEach(function(item) {
148 keys.push(item.getAttribute('data-key'));
151 if (keys.indexOf(key) !== -1) {
152 showModal('<%:Add key%>', [
153 E('div', { class: 'alert-message warning' }, _('The given SSH public key has already been added.')),
154 E('div', { class: 'right' }, E('div', { class: 'btn', click: hideModal }, _('Close')))
158 showModal('<%:Add key%>', [
159 E('div', { class: 'alert-message warning' }, _('The given SSH public key is invalid. Please supply proper public RSA or ECDSA keys.')),
160 E('div', { class: 'right' }, E('div', { class: 'btn', click: hideModal }, _('Close')))
170 function removeKey(ev) {
171 var list = findParent(ev.target, '.cbi-dynlist'),
172 delkey = ev.target.getAttribute('data-key'),
175 list.querySelectorAll('.item').forEach(function(item) {
176 var key = item.getAttribute('data-key');
181 showModal('<%:Delete key%>', [
182 E('div', _('Do you really want to delete the following SSH key?')),
184 E('div', { class: 'right' }, [
185 E('div', { class: 'btn', click: hideModal }, _('Cancel')),
187 E('div', { class: 'btn danger', click: function(ev) { saveKeys(keys) } }, _('Delete key')),
192 function dragKey(ev) {
193 ev.stopPropagation();
195 ev.dataTransfer.dropEffect = 'copy';
198 function dropKey(ev) {
199 var file = ev.dataTransfer.files[0],
200 input = ev.currentTarget.querySelector('input[type="text"]'),
201 reader = new FileReader();
204 reader.onload = function(rev) {
205 input.value = rev.target.result.trim();
210 reader.readAsText(file);
213 ev.stopPropagation();
217 window.addEventListener('dragover', function(ev) { ev.preventDefault() });
218 window.addEventListener('drop', function(ev) { ev.preventDefault() });
220 requestAnimationFrame(function() {
221 XHR.get('<%=url("admin/system/admin/sshkeys/json")%>', null, function(xhr, keys) {
227 <div class="cbi-map">
228 <h2><%:SSH-Keys%></h2>
230 <div class="cbi-section-descr">
231 <%_Public keys allow for the passwordless SSH logins with a higher security compared to the use of plain passwords. In order to upload a new key to the device, paste an OpenSSH compatible public key line or drag a <code>.pub</code> file into the input field.%>
234 <div class="cbi-section-node">
235 <div class="cbi-dynlist" name="sshkeys">
236 <p class="spinning"><%:Loading SSH keys…%></p>
237 <div class="add-item" ondragover="dragKey(event)" ondrop="dropKey(event)">
238 <input class="cbi-input-text" type="text" placeholder="<%:Paste or drag SSH key file…%>" onkeydown="if (event.keyCode === 13) addKey(event)" /><!--
239 --><div class="cbi-button" onclick="addKey(event)"><%:Add key%></div>