From 593a6e6f83eba6de33cd950cbc3a30fb3f2e0659 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 7 Feb 2020 20:59:21 -0800 Subject: [PATCH] BLE is now secured to require pairing with PIN --- TODO.md | 4 +- lib/BluetoothOTA/src/BluetoothUtil.cpp | 226 ++++++++++++++++--------- platformio.ini | 1 + src/main.ino | 3 + src/screen.cpp | 60 ++++++- src/screen.h | 8 +- 6 files changed, 214 insertions(+), 88 deletions(-) diff --git a/TODO.md b/TODO.md index 3d43d04f..41d7c149 100644 --- a/TODO.md +++ b/TODO.md @@ -2,11 +2,12 @@ * have node info screen show real info (including distance and heading) * very occasionally send our position and user packet (if for nothing else so that other nodes update last_seen) -* save our node db on entry to sleep * make a screen for bluetooth not yet configured # Medium priority +* only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app. +* use https://platformio.org/lib/show/1260/OneButton * make an about to sleep screen * make a no bluetooth configured yet screen * don't send location packets if we haven't moved @@ -107,3 +108,4 @@ until the phone pulls those packets. Ever so often power on bluetooth just so w * switch to my gui layout manager * make basic gui. different screens: debug, one page for each user in the user db, last received text message * make button press cycle between screens +* save our node db on entry to sleep \ No newline at end of file diff --git a/lib/BluetoothOTA/src/BluetoothUtil.cpp b/lib/BluetoothOTA/src/BluetoothUtil.cpp index 89211eec..71769d8d 100644 --- a/lib/BluetoothOTA/src/BluetoothUtil.cpp +++ b/lib/BluetoothOTA/src/BluetoothUtil.cpp @@ -5,19 +5,21 @@ #include #include #include "configuration.h" +#include "screen.h" -static BLECharacteristic SWVersionCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); -static BLECharacteristic ManufacturerCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); -static BLECharacteristic HardwareVersionCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); +static BLECharacteristic SWVersionCharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); +static BLECharacteristic ManufacturerCharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); +static BLECharacteristic HardwareVersionCharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); //static BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), BLECharacteristic::PROPERTY_READ); /** * Create standard device info service **/ -BLEService *createDeviceInfomationService(BLEServer* server, std::string hwVendor, std::string swVersion) { - BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t) ESP_GATT_UUID_DEVICE_INFO_SVC)); +BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion) +{ + BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); - /* + /* * Mandatory characteristic for device info service? BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, BLECharacteristic::PROPERTY_READ); @@ -26,128 +28,196 @@ BLEService *createDeviceInfomationService(BLEServer* server, std::string hwVendo uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); */ - SWVersionCharacteristic.setValue(swVersion); - deviceInfoService->addCharacteristic(&SWVersionCharacteristic); - ManufacturerCharacteristic.setValue(hwVendor); - deviceInfoService->addCharacteristic(&ManufacturerCharacteristic); - HardwareVersionCharacteristic.setValue("1.0"); - deviceInfoService->addCharacteristic(&HardwareVersionCharacteristic); - //SerialNumberCharacteristic.setValue("FIXME"); - //deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); + SWVersionCharacteristic.setValue(swVersion); + deviceInfoService->addCharacteristic(&SWVersionCharacteristic); + ManufacturerCharacteristic.setValue(hwVendor); + deviceInfoService->addCharacteristic(&ManufacturerCharacteristic); + HardwareVersionCharacteristic.setValue("1.0"); + deviceInfoService->addCharacteristic(&HardwareVersionCharacteristic); + //SerialNumberCharacteristic.setValue("FIXME"); + //deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); - // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ); - // m_manufacturerCharacteristic->setValue(name); + // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ); + // m_manufacturerCharacteristic->setValue(name); - - /* add these later? + /* add these later? ESP_GATT_UUID_SYSTEM_ID */ - // caller must call service->start(); - return deviceInfoService; + // caller must call service->start(); + return deviceInfoService; } bool _BLEClientConnected = false; -class MyServerCallbacks : public BLEServerCallbacks { - void onConnect(BLEServer* pServer) { - _BLEClientConnected = true; - }; +class MyServerCallbacks : public BLEServerCallbacks +{ + void onConnect(BLEServer *pServer) + { + _BLEClientConnected = true; + }; - void onDisconnect(BLEServer* pServer) { - _BLEClientConnected = false; - } + void onDisconnect(BLEServer *pServer) + { + _BLEClientConnected = false; + } }; // Help routine to add a description to any BLECharacteristic and add it to the service -void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description) { - BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t) ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1); +// We default to require an encrypted BOND for all these these characterstics +void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description) +{ + c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1); assert(desc); + desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); desc->setValue(description); c->addDescriptor(desc); service->addCharacteristic(c); } -static BLECharacteristic BatteryLevelCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); +static BLECharacteristic BatteryLevelCharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); /** * Create a battery level service */ -BLEService *createBatteryService(BLEServer* server) { - // Create the BLE Service - BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F)); +BLEService *createBatteryService(BLEServer *server) +{ + // Create the BLE Service + BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F)); - addWithDesc(pBattery, &BatteryLevelCharacteristic, "Percentage 0 - 100"); - BatteryLevelCharacteristic.addDescriptor(new BLE2902()); // Needed so clients can request notification + addWithDesc(pBattery, &BatteryLevelCharacteristic, "Percentage 0 - 100"); + BatteryLevelCharacteristic.addDescriptor(new BLE2902()); // Needed so clients can request notification - // I don't think we need to advertise this - // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); - pBattery->start(); + // I don't think we need to advertise this + // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); + pBattery->start(); - return pBattery; + return pBattery; } - /** * Update the battery level we are currently telling clients. * level should be a pct between 0 and 100 */ -void updateBatteryLevel(uint8_t level) { - // Pretend to update battery levels - fixme do elsewhere - BatteryLevelCharacteristic.setValue(&level, 1); - BatteryLevelCharacteristic.notify(); +void updateBatteryLevel(uint8_t level) +{ + // Pretend to update battery levels - fixme do elsewhere + BatteryLevelCharacteristic.setValue(&level, 1); + BatteryLevelCharacteristic.notify(); } +void dumpCharacteristic(BLECharacteristic *c) +{ + std::string value = c->getValue(); -void dumpCharacteristic(BLECharacteristic *c) { - std::string value = c->getValue(); + if (value.length() > 0) + { + DEBUG_MSG("New value: "); + for (int i = 0; i < value.length(); i++) + DEBUG_MSG("%c", value[i]); - if (value.length() > 0) { - DEBUG_MSG("New value: "); - for (int i = 0; i < value.length(); i++) - DEBUG_MSG("%c", value[i]); - - DEBUG_MSG("\n"); - } + DEBUG_MSG("\n"); + } } /** converting endianness pull out a 32 bit value */ -uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue) { - std::string value = c->getValue(); - uint32_t r = defaultValue; +uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue) +{ + std::string value = c->getValue(); + uint32_t r = defaultValue; - if(value.length() == 4) - r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL); - - return r; + if (value.length() == 4) + r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL); + + return r; } +class MySecurity : public BLESecurityCallbacks +{ -BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swVersion) { - BLEDevice::init(deviceName); - // Create the BLE Server - BLEServer *pServer = BLEDevice::createServer(); - pServer->setCallbacks(new MyServerCallbacks()); + bool onConfirmPIN(uint32_t pin) + { + Serial.printf("onConfirmPIN %u\n", pin); + return false; + } - BLEService *pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion); + uint32_t onPassKeyRequest() + { + Serial.println("onPassKeyRequest"); + return 123511; // not used + } - // We now let users create the battery service only if they really want (not all devices have a battery) - // BLEService *pBattery = createBatteryService(pServer); + void onPassKeyNotify(uint32_t pass_key) + { + Serial.printf("onPassKeyNotify %u\n", pass_key); + screen_start_bluetooth(pass_key); + } - BLEService *pUpdate = createUpdateService(pServer); // We need to advertise this so our android ble scan operation can see it - pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); + bool onSecurityRequest() + { + Serial.println("onSecurityRequest"); + return true; + } - // start all our services (do this after creating all of them) - pDevInfo->start(); - pUpdate->start(); + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) + { + if (cmpl.success) + { + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + Serial.printf(" onAuthenticationComplete -> success size: %d\n", length); + } + else + { + Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason); + } - // Start advertising - pServer->getAdvertising()->start(); + // Remove our custom screen + screen_set_frames(); + } +}; - return pServer; +BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swVersion) +{ + BLEDevice::init(deviceName); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + // Create the BLE Server + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + BLEService *pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion); + + // We now let users create the battery service only if they really want (not all devices have a battery) + // BLEService *pBattery = createBatteryService(pServer); + + BLEService *pUpdate = createUpdateService(pServer); // We need to advertise this so our android ble scan operation can see it + pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); + + // start all our services (do this after creating all of them) + pDevInfo->start(); + pUpdate->start(); + + // Start advertising + pServer->getAdvertising()->start(); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + return pServer; } // Called from loop -void loopBLE() { - bluetoothRebootCheck(); +void loopBLE() +{ + bluetoothRebootCheck(); } diff --git a/platformio.ini b/platformio.ini index 18747872..f0157a76 100644 --- a/platformio.ini +++ b/platformio.ini @@ -61,6 +61,7 @@ lib_deps = ESP8266_SSD1306 AXP202X_Library SPI + OneButton CRC32 ; explicitly needed because dependency is missing in the ble ota update lib Wire ; explicitly needed here because the AXP202 library forgets to add it diff --git a/src/main.ino b/src/main.ino index 6c68bf62..0cf96217 100644 --- a/src/main.ino +++ b/src/main.ino @@ -31,6 +31,7 @@ #include "MeshService.h" #include "GPS.h" #include "screen.h" +#include "NodeDB.h" #ifdef T_BEAM_V10 #include "axp20x.h" @@ -66,6 +67,8 @@ void doDeepSleep(uint64_t msecToWake) // Put radio in sleep mode (will still draw power but only 0.2uA) service.radio.sleep(); + nodeDB.saveToDisk(); + #ifdef RESET_OLED digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power #endif diff --git a/src/screen.cpp b/src/screen.cpp index ecc43669..1f79aee4 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -66,6 +66,26 @@ void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, ui.disableIndicator(); } +static char btPIN[16] = "888888"; + +void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file + // Besides the default fonts there will be a program to convert TrueType fonts into this format + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(ArialMT_Plain_16); + display->drawString(64 + x, 2 + y, "Bluetooth"); + + display->setFont(ArialMT_Plain_10); + display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT + y, "Enter this code"); + + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(ArialMT_Plain_24); + display->drawString(64 + x, 22 + y, btPIN); + + ui.disableIndicator(); +} + void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file @@ -209,12 +229,13 @@ OverlayCallback overlays[] = {/* msOverlay */}; const int frameCount = sizeof(frames) / sizeof(frames[0]); const int overlaysCount = sizeof(overlays) / sizeof(overlays[0]); +#if 0 void _screen_header() { if (!disp) return; -#if 0 + // Message count //snprintf(buffer, sizeof(buffer), "#%03d", ttn_get_count() % 1000); //display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -229,8 +250,8 @@ void _screen_header() char buffer[10]; display->drawString(display->getWidth() - SATELLITE_IMAGE_WIDTH - 4, 2, itoa(gps.satellites.value(), buffer, 10)); display->drawXbm(display->getWidth() - SATELLITE_IMAGE_WIDTH, 0, SATELLITE_IMAGE_WIDTH, SATELLITE_IMAGE_HEIGHT, SATELLITE_IMAGE); -#endif } +#endif void screen_off() { @@ -259,10 +280,7 @@ static void screen_print(const char *text, uint8_t x, uint8_t y, uint8_t alignme dispdev.drawString(x, y, text); } -static void screen_print(const char *text, uint8_t x, uint8_t y) -{ - screen_print(text, x, y, TEXT_ALIGN_LEFT); -} + void screen_print(const char *text) { @@ -317,6 +335,8 @@ void screen_setup() #endif } +static bool showingBluetooth; + uint32_t screen_loop() { if (!disp) @@ -356,19 +376,43 @@ uint32_t screen_loop() ui.update(); // Once we finish showing the bootscreen, remove it from the loop - if (showingBootScreen && ui.getUiState()->currentFrame == 1) + if (showingBootScreen && !showingBluetooth && ui.getUiState()->currentFrame == 1) { showingBootScreen = false; - ui.setFrames(nonBootFrames, frameCount - 1); + screen_set_frames(); } // If we are scrolling do 30fps, otherwise just 1 fps (to save CPU) return (ui.getUiState()->frameState == IN_TRANSITION ? 10 : 500); } +// Show the bluetooth PIN screen +void screen_start_bluetooth(uint32_t pin) { + static FrameCallback btFrames[] = { drawFrameBluetooth }; + + snprintf(btPIN, sizeof(btPIN), "%06d", pin); + + DEBUG_MSG("showing bluetooth screen\n"); + showingBluetooth = true; + + //screen_on(); // make sure the screen is not asleep - FIXME, this causes crap to draw on the screen + ui.disableAutoTransition(); // we now require presses + ui.setFrames(btFrames, 1); // Just show the bluetooth frame + // we rely on our main loop to show this screen (because we are invoked deep inside of bluetooth callbacks) + // ui.update(); // manually draw once, because I'm not sure if loop is getting called +} + +// restore our regular frame list +void screen_set_frames() { + DEBUG_MSG("showing standard frames\n"); + ui.setFrames(nonBootFrames, frameCount - 1); + showingBluetooth = false; +} /// handle press of the button void screen_press() { + //screen_start_bluetooth(123456); + // Once the user presses a button, stop auto scrolling between screens ui.disableAutoTransition(); // we now require presses ui.nextFrame(); diff --git a/src/screen.h b/src/screen.h index 6909ce4c..ebd6f273 100644 --- a/src/screen.h +++ b/src/screen.h @@ -5,4 +5,10 @@ void screen_print(const char * text); /// @return how many msecs can we sleep before we want service again uint32_t screen_loop(); -void screen_setup(), screen_on(), screen_off(), screen_press(); \ No newline at end of file +void screen_setup(), screen_on(), screen_off(), screen_press(); + +// Show the bluetooth PIN screen +void screen_start_bluetooth(uint32_t pin); + +// restore our regular frame list +void screen_set_frames(); \ No newline at end of file