import 'preact/devtools' import { h, Component } from 'preact' import htm from 'htm' import Crypto from 'crypto' const html = htm.bind(h) export class Keys extends Component { constructor(props) {//{{{ super(props) this.state = { create: false, } props.nodeui.retrieveKeys() }//}}} render({ nodeui }, { create }) {//{{{ let keys = nodeui.keys.value .sort((a,b)=>{ if(a.description < b.description) return -1 if(a.description > b.description) return 1 return 0 }) .map(key=> html`<${KeyComponent} key=${`key-${key.ID}`} model=${key} />` ) let createButton = '' let createComponents = '' if(create) { createComponents = html`

New key

` } else { createButton = html`
` } return html`

Encryption keys

Unlock a key by clicking its name. Lock it by clicking it again.

Copy the key and store it in a very secure place to have a way to access notes in case the password is forgotten, or database is corrupted.

Click "View key" after unlocking it.

${createButton} ${createComponents}

Keys

${keys}
` }//}}} generateKey() {//{{{ let keyTextarea = document.getElementById('key-key') let key = sjcl.codec.hex.fromBits(Crypto.generate_key()).replace(/(....)/g, '$1 ').trim() keyTextarea.value = key }//}}} validateNewKey() {//{{{ let keyDescription = document.getElementById('key-description').value let keyTextarea = document.getElementById('key-key').value let pass1 = document.getElementById('key-pass1').value let pass2 = document.getElementById('key-pass2').value if(keyDescription.trim() == '') throw new Error('The key has to have a description') if(pass1.trim() == '' || pass1.length < 4) throw new Error('The password has to be at least 4 characters long.') if(pass1 != pass2) throw new Error(`Passwords doesn't match`) let cleanKey = keyTextarea.replace(/\s+/g, '') if(!cleanKey.match(/^[0-9a-f]{64}$/i)) throw new Error('Invalid key - has to be 64 characters of 0-9 and A-F') }//}}} createKey() {//{{{ try { this.validateNewKey() let description = document.getElementById('key-description').value let keyAscii = document.getElementById('key-key').value let pass1 = document.getElementById('key-pass1').value // Key in hex taken from user. let actual_key = sjcl.codec.hex.toBits(keyAscii.replace(/\s+/g, '')) // Key generated from password, used to encrypt the actual key. let pass_gen = Crypto.pass_to_key(pass1) let crypto = new Crypto(pass_gen.key) let encrypted_actual_key = crypto.encrypt(actual_key, 0x1n, false) // Database value is salt + actual key, needed to generate the same key from the password. let db_encoded = sjcl.codec.hex.fromBits( pass_gen.salt.concat(encrypted_actual_key) ) // Create on server. window._app.current.request('/key/create', { description, key: db_encoded, }) .then(res=>{ let key = new Key(res.Key, this.props.nodeui.keyCounter) this.props.nodeui.keys.value = this.props.nodeui.keys.value.concat(key) }) .catch(window._app.current.responseError) } catch(err) { alert(err.message) return } }//}}} } export class Key { constructor(data, counter_callback) {//{{{ this.ID = data.ID this.description = data.Description this.encryptedKey = data.Key this.key = null this._counter_cbk = counter_callback let hex_key = window.sessionStorage.getItem(`key-${this.ID}`) if(hex_key) this.key = sjcl.codec.hex.toBits(hex_key) }//}}} status() {//{{{ if(this.key === null) return 'locked' return 'unlocked' }//}}} lock() {//{{{ this.key = null window.sessionStorage.removeItem(`key-${this.ID}`) }//}}} unlock(password) {//{{{ let db = sjcl.codec.hex.toBits(this.encryptedKey) let salt = db.slice(0, 4) let pass_key = Crypto.pass_to_key(password, salt) let crypto = new Crypto(pass_key.key) this.key = crypto.decrypt(sjcl.codec.base64.fromBits(db.slice(4))) window.sessionStorage.setItem(`key-${this.ID}`, sjcl.codec.hex.fromBits(this.key)) }//}}} async counter() {//{{{ return this._counter_cbk() }//}}} } export class KeyComponent extends Component { constructor({ model }) {//{{{ super({ model }) this.state = { show_key: false, } }//}}} render({ model }, { show_key }) {//{{{ let status = '' switch(model.status()) { case 'locked': status = html`
` break case 'unlocked': status = html`
` break } let hex_key = '' if(show_key) { if(model.status() == 'locked') hex_key = html`
Unlock key first
` else { let key = sjcl.codec.hex.fromBits(model.key) key = key.replace(/(....)/g, "$1 ").trim() hex_key = html`
${key}
` } } let unlocked = model.status()=='unlocked' return html`
this.toggle()}>${status}
this.toggle()}>${model.description}
this.toggleViewKey()}>${unlocked ? 'View key' : ''}
${hex_key} ` }//}}} toggle() {//{{{ if(this.props.model.status() == 'locked') this.unlock() else this.lock() }//}}} lock() {//{{{ this.props.model.lock() this.forceUpdate() }//}}} unlock() {//{{{ let pass = prompt("Password") if(!pass) return try { this.props.model.unlock(pass) this.forceUpdate() } catch(err) { alert(err) } }//}}} toggleViewKey() {//{{{ this.setState({ show_key: !this.state.show_key }) }//}}} } // vim: foldmethod=marker