diff --git a/static/components/serial-signer.js b/static/components/serial-signer.js
index eb46116..e5a90a6 100644
--- a/static/components/serial-signer.js
+++ b/static/components/serial-signer.js
@@ -10,6 +10,7 @@ window.app.component('serial-signer', {
writer: null,
readableStreamClosed: null,
reader: null,
+ closingSerialPort: false,
receivedData: '',
config: {},
decryptionKey: null,
@@ -49,25 +50,7 @@ window.app.component('serial-signer', {
},
tx: null, // todo: move to hww
- showConsole: false,
- showPairedDevices: true
- }
- },
-
- computed: {
- pairedDevices: {
- cache: false,
- get: function () {
- return (
- JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) || []
- )
- },
- set: function (devices) {
- window.localStorage.setItem(
- 'lnbits-paired-devices',
- JSON.stringify(devices)
- )
- }
+ showConsole: false
}
},
@@ -98,6 +81,8 @@ window.app.component('serial-signer', {
this.selectedPort.addEventListener('disconnect', () => {
this.selectedPort = null
+ this.sharedSecret = null
+ this.decryptionKey = null
this.hww.authenticated = false
this.$q.notify({
type: 'warning',
@@ -111,7 +96,7 @@ window.app.component('serial-signer', {
// do not await
this.startSerialPortReading()
// wait to init
- sleep(1000)
+ await sleep(1000)
const textEncoder = new TextEncoderStream()
this.writableStreamClosed = textEncoder.readable.pipeTo(
@@ -121,7 +106,6 @@ window.app.component('serial-signer', {
this.writer = textEncoder.writable.getWriter()
await this.hwwPing()
- this.$emit('device:connected', 'usb-device')
return true
} catch (error) {
@@ -135,20 +119,16 @@ window.app.component('serial-signer', {
return false
}
},
- openSerialPortConfig: async function (deviceId) {
- const device = this.getPairedDevice(deviceId)
- if (device) {
- this.config = device.config
- } else {
- this.config = {...HWW_DEFAULT_CONFIG}
- }
+ openSerialPortConfig: async function () {
+ this.config = {...HWW_DEFAULT_CONFIG}
this.hww.showConfigDialog = true
},
closeSerialPort: async function () {
+ this.closingSerialPort = true
try {
- if (this.writer) this.writer.close()
+ if (this.writer) await this.writer.close()
if (this.writableStreamClosed) await this.writableStreamClosed
- if (this.reader) this.reader.cancel()
+ if (this.reader) await this.reader.cancel()
if (this.readableStreamClosed)
await this.readableStreamClosed.catch(() => {
/* Ignore the error */
@@ -168,7 +148,14 @@ window.app.component('serial-signer', {
})
} finally {
this.selectedPort = null
+ this.writer = null
+ this.reader = null
+ this.writableStreamClosed = null
+ this.readableStreamClosed = null
+ this.sharedSecret = null
+ this.decryptionKey = null
this.hww.authenticated = false
+ this.closingSerialPort = false
}
},
@@ -232,18 +219,21 @@ window.app.component('serial-signer', {
const {value, done} = await readStringUntil('\n')
if (value) {
const {command, commandData} = await this.extractCommand(value)
- this.handleSerialPortResponse(command, commandData)
+ await this.handleSerialPortResponse(command, commandData)
this.updateSerialPortConsole(command)
}
if (done) return
}
} catch (error) {
- this.$q.notify({
- type: 'warning',
- message: 'Serial port communication error!',
- caption: `${error}`,
- timeout: 10000
- })
+ if (!this.closingSerialPort && this.selectedPort === port) {
+ this.$q.notify({
+ type: 'warning',
+ message: 'Serial port communication error!',
+ caption: `${error}`,
+ timeout: 10000
+ })
+ }
+ return
}
}
},
@@ -252,10 +242,7 @@ window.app.component('serial-signer', {
switch (command) {
case COMMAND_PING:
- this.handlePingResponse(commandData)
- break
- case COMMAND_CHECK_PAIRING:
- this.handleCheckPairingResponse(commandData)
+ await this.handlePingResponse(commandData)
break
case COMMAND_SIGN_PSBT:
this.handleSignResponse(commandData)
@@ -279,7 +266,7 @@ window.app.component('serial-signer', {
this.handleShowSeedResponse(commandData)
break
case COMMAND_PAIR:
- this.handlePairResponse(commandData)
+ await this.handlePairResponse(commandData)
break
case COMMAND_LOG:
console.log(` %c${commandData}`, 'background: #222; color: #bada55')
@@ -310,8 +297,6 @@ window.app.component('serial-signer', {
},
hwwPing: async function () {
try {
- // Send an empty ping. The serial port buffer might have some jubk data. Flush it.
- await this.sendCommandClearText(COMMAND_PING)
await this.sendCommandClearText(COMMAND_PING, [window.location.host])
} catch (error) {
this.$q.notify({
@@ -322,7 +307,7 @@ window.app.component('serial-signer', {
})
}
},
- handlePingResponse: function (res = '') {
+ handlePingResponse: async function (res = '') {
const [status, deviceId] = res.split(' ')
this.deviceId = deviceId
@@ -335,16 +320,7 @@ window.app.component('serial-signer', {
return
}
- const device = this.getPairedDevice(deviceId)
-
- if (device) {
- this.sharedSecret = nobleSecp256k1.utils.hexToBytes(
- device.sharedSecretHex
- )
- this.hwwCheckPairing()
- } else {
- this.hwwPair()
- }
+ await this.hwwPair()
},
hwwShowPasswordDialog: async function () {
try {
@@ -411,9 +387,6 @@ window.app.component('serial-signer', {
},
hwwConfigAndConnect: async function () {
this.hww.showConfigDialog = false
- if (this.config.deviceId) {
- this.updatePairedDeviceConfig(this.config.deviceId, this.config)
- }
await this.openSerialPort(this.config)
return true
},
@@ -584,63 +557,6 @@ window.app.component('serial-signer', {
timeout: 10000
})
},
- hwwCheckPairing: async function () {
- const iv = window.crypto.getRandomValues(new Uint8Array(16))
- const encrypted = await this.encryptMessage(
- this.sharedSecret, // todo: revisit
- iv,
- PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT
- )
-
- const encryptedHex = nobleSecp256k1.utils.bytesToHex(encrypted)
- const encryptedIvHex = nobleSecp256k1.utils.bytesToHex(iv)
- try {
- await this.sendCommandClearText(COMMAND_CHECK_PAIRING, [
- encryptedHex + encryptedIvHex
- ])
- } catch (error) {
- this.$q.notify({
- type: 'warning',
- message: 'Failed to check secure connection!',
- caption: `${error}`,
- timeout: 10000
- })
- }
- },
- handleCheckPairingResponse: async function (res = '') {
- const [statusCode, message] = res.split(' ')
- switch (statusCode) {
- case '0':
- const controlText = await this.decryptData(message)
- if (controlText == PAIRING_CONTROL_TEXT) {
- this.$q.notify({
- type: 'positive',
- message: 'Re-paired with success!',
- timeout: 10000
- })
- } else {
- this.$q.notify({
- type: 'warning',
- message: 'Re-pairing failed!',
- caption: 'Remove (forget) device and try again!',
- timeout: 10000
- })
- }
- break
- case '1':
- this.closeSerialPort()
- this.$q.notify({
- type: 'warning',
- message: 'Re-pairing failed. Remove (forget) device and try again!',
- caption: `Error: ${message}`,
- timeout: 10000
- })
- break
- default:
- // noting to do here yet
- break
- }
- },
hwwPair: async function () {
try {
this.decryptionKey = nobleSecp256k1.utils.randomPrivateKey()
@@ -680,9 +596,17 @@ window.app.component('serial-signer', {
if (!data) errorMessage = 'Failed to exchange DH secret!'
break
case '1':
- errorMessage =
- 'Device pairing only possible in the first 10 seconds after start-up!'
- captionMessage = 'Restart and try again'
+ if (data === 'connection_period_expired') {
+ errorMessage =
+ 'Device pairing only possible during the startup countdown!'
+ captionMessage = 'Restart the device and try again'
+ } else if (data === 'rng_failure') {
+ errorMessage = 'Device hardware RNG health check failed!'
+ captionMessage = 'Pairing was safely refused by the device'
+ } else {
+ errorMessage = 'Device refused pairing'
+ captionMessage = data || 'Unknown device error'
+ }
break
default:
@@ -718,12 +642,7 @@ window.app.component('serial-signer', {
LNbits.utils
.confirmDialog('Confirm code from display: ' + fingerprint)
.onOk(() => {
- this.addPairedDevice(
- this.deviceId,
- nobleSecp256k1.utils.bytesToHex(this.sharedSecret),
- this.config
- )
-
+ this.$emit('device:connected', 'usb-device')
this.$q.notify({
type: 'positive',
message: 'Paired with device!',
@@ -899,8 +818,7 @@ window.app.component('serial-signer', {
command === COMMAND_PAIR ||
command === COMMAND_LOG ||
command === COMMAND_PASSWORD_CLEAR ||
- command === COMMAND_PING ||
- command === COMMAND_CHECK_PAIRING
+ command === COMMAND_PING
)
return {command, commandData}
@@ -954,47 +872,9 @@ window.app.component('serial-signer', {
const aesCbc = new aesjs.ModeOfOperation.cbc(key, iv)
const decryptedBytes = aesCbc.decrypt(encryptedBytes)
return decryptedBytes
- },
-
- getPairedDevice: function (deviceId) {
- return this.pairedDevices.find(d => d.id === deviceId)
- },
- removePairedDevice: function (deviceId) {
- const devices = this.pairedDevices
- const deviceIndex = devices.findIndex(d => d.id === deviceId)
- if (deviceIndex !== -1) {
- devices.splice(deviceIndex, 1)
- }
- this.pairedDevices = devices
- this.showPairedDevices = false
- setTimeout(() => {
- // force UI refresh
- this.showPairedDevices = true
- })
- },
- addPairedDevice: function (deviceId, sharedSecretHex, config) {
- const devices = this.pairedDevices
- config.deviceId = deviceId
- devices.unshift({
- id: deviceId,
- sharedSecretHex: sharedSecretHex,
- pairingDate: new Date().toISOString(),
- config
- })
- this.pairedDevices = devices
- this.showPairedDevices = false
- setTimeout(() => {
- // force UI refresh
- this.showPairedDevices = true
- })
- },
- updatePairedDeviceConfig(deviceId, config) {
- const device = this.getPairedDevice(deviceId)
- if (device) {
- this.removePairedDevice(deviceId)
- this.addPairedDevice(deviceId, device.sharedSecretHex, config)
- }
}
},
- created: async function () {}
+ created: async function () {
+ window.localStorage.removeItem('lnbits-paired-devices')
+ }
})
diff --git a/static/index.vue b/static/index.vue
index fa6b4cd..6dac05d 100644
--- a/static/index.vue
+++ b/static/index.vue
@@ -2010,37 +2010,6 @@
-
-
-
-
- {{ device.id }}
-
-
-
-
-
-
{
let fulliness = []
const readStringUntil = async (separator = '\n') => {
- if (fulliness.length) return fulliness.shift().trim()
+ if (fulliness.length)
+ return {value: fulliness.shift().trim(), done: false}
const chunks = []
if (partialChunk) {
// leftovers from previous read