Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp

// List of extensions which should be recommended for users of this workspace.
"recommendations": [

],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [

]
}
42 changes: 35 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ The flipper stores your usernames and password and can write them on your PC act
- The flipper shows a lists of website names
- If OK is clicked, the flipper will remove the selected line from the credentials file

### Storage
Credentials are stored as ```service,username,password``` in a .txt file.
```\``` and ```,``` needs to be escaped as ```\\``` and ```\,``` if you handle the file manually.
### Storage (updated)
Credentials are stored encrypted as hex (AES-128-CBC via FlipCrypt).
Before encryption we concatenate `service`, `username`, and `password` with a safe non‑printable delimiter (0x1E),
so commas/symbols inside fields do not need escaping.

### USB and BLE HID
- USB: temporarily switches to USB HID, types the data, then returns to the default Flipper USB profile.
- BLE: types over BLE HID (advertises as "Control <flipper name>") and works on iOS/Android/PC.

### Keyboard layout
Keyboard layout files (`.kl`) from BadUSB are supported and loaded after passcode unlock based on
`/ext/apps_data/PasswordManager/config.conf`.

## Add new password

Expand All @@ -28,10 +37,29 @@ Credentials are stored as ```service,username,password``` in a .txt file.

![usage](img/login.gif)

## Show passwords

![usage](img/passwords.gif)

## Delete passwords

![usage](img/delete.gif)

## BLE
Typing over BLE HID (iOS/Android/PC). Advertises as "Control <flipper name>".
LED blinking blue = discovery
LED solid blue = paired

![usage](img/ble.gif)

## Passcode
Initial passcode setup and unlock flow (layout loads after unlock).

![usage](img/pcode.gif)

## Options / Layout
Select keyboard layout (.kl). BT indicator shows when BLE is active.

![usage](img/keyboard.gif)

## Credits
- Momentum firmware (Firmware used for development and testing)
- FlipCrypt (tiny-AES-c) for AES-128-CBC encryption library
- Rrycbarm for the original Password Manager
- TOTP app for inspiration on passcode flow, BLE integration, and USB fixes
17 changes: 14 additions & 3 deletions application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ App(
name="Password Manager",
apptype=FlipperAppType.EXTERNAL,
entry_point="password_manager_app",
requires=["gui", "usb_hid"],
stack_size=2 * 1024,
requires=["gui", "usb_hid", "bt"],
fap_libs=["ble_profile"],
stack_size=4 * 1024,
fap_category="Tools",
fap_icon="key.png",
fap_description="This app stores your usernames and password and can write them on your PC acting as a keyboard",
fap_version=(1, 2)
fap_version=(1, 3),
fap_private_libs=[
Lib(
name="FlipCrypt",
sources=[
"ciphers/aes.c",
"hashes/sha2.c",
],
cincludes=["lib/FlipCrypt/ciphers", "lib/FlipCrypt/hashes"],
),
],
)
140 changes: 137 additions & 3 deletions badusb/badusb.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,104 @@
#include "badusb.h"
#include <storage/storage.h>

// Layout system based on official BadUSB implementation
static uint16_t keyboard_layout[128];
static bool layout_loaded = false;

// Initialize with default US layout (same as official BadUSB)
static void set_default_keyboard_layout(void) {
// Clear layout
memset(keyboard_layout, HID_KEYBOARD_NONE, sizeof(keyboard_layout));

// Copy default ASCII map (this would normally come from hid_asciimap)
// For now, use basic US layout mappings for common characters
keyboard_layout['a'] = HID_KEYBOARD_A;
keyboard_layout['b'] = HID_KEYBOARD_B;
keyboard_layout['c'] = HID_KEYBOARD_C;
keyboard_layout['d'] = HID_KEYBOARD_D;
keyboard_layout['e'] = HID_KEYBOARD_E;
keyboard_layout['f'] = HID_KEYBOARD_F;
keyboard_layout['g'] = HID_KEYBOARD_G;
keyboard_layout['h'] = HID_KEYBOARD_H;
keyboard_layout['i'] = HID_KEYBOARD_I;
keyboard_layout['j'] = HID_KEYBOARD_J;
keyboard_layout['k'] = HID_KEYBOARD_K;
keyboard_layout['l'] = HID_KEYBOARD_L;
keyboard_layout['m'] = HID_KEYBOARD_M;
keyboard_layout['n'] = HID_KEYBOARD_N;
keyboard_layout['o'] = HID_KEYBOARD_O;
keyboard_layout['p'] = HID_KEYBOARD_P;
keyboard_layout['q'] = HID_KEYBOARD_Q;
keyboard_layout['r'] = HID_KEYBOARD_R;
keyboard_layout['s'] = HID_KEYBOARD_S;
keyboard_layout['t'] = HID_KEYBOARD_T;
keyboard_layout['u'] = HID_KEYBOARD_U;
keyboard_layout['v'] = HID_KEYBOARD_V;
keyboard_layout['w'] = HID_KEYBOARD_W;
keyboard_layout['x'] = HID_KEYBOARD_X;
keyboard_layout['y'] = HID_KEYBOARD_Y;
keyboard_layout['z'] = HID_KEYBOARD_Z;

// Numbers
keyboard_layout['1'] = HID_KEYBOARD_1;
keyboard_layout['2'] = HID_KEYBOARD_2;
keyboard_layout['3'] = HID_KEYBOARD_3;
keyboard_layout['4'] = HID_KEYBOARD_4;
keyboard_layout['5'] = HID_KEYBOARD_5;
keyboard_layout['6'] = HID_KEYBOARD_6;
keyboard_layout['7'] = HID_KEYBOARD_7;
keyboard_layout['8'] = HID_KEYBOARD_8;
keyboard_layout['9'] = HID_KEYBOARD_9;
keyboard_layout['0'] = HID_KEYBOARD_0;

// Common symbols (US layout)
keyboard_layout[' '] = HID_KEYBOARD_SPACEBAR;
keyboard_layout['\n'] = HID_KEYBOARD_RETURN;
keyboard_layout['\r'] = HID_KEYBOARD_RETURN;
keyboard_layout['\t'] = HID_KEYBOARD_TAB;

layout_loaded = true;
}

// Load keyboard layout from .kl file (same as official BadUSB)
void load_keyboard_layout(const char* layout_path) {
if(!layout_path || strlen(layout_path) == 0) {
set_default_keyboard_layout();
return;
}

Storage* storage = furi_record_open(RECORD_STORAGE);
File* layout_file = storage_file_alloc(storage);

if(storage_file_open(layout_file, layout_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
uint16_t layout[128];
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
memcpy(keyboard_layout, layout, sizeof(layout));
layout_loaded = true;
} else {
set_default_keyboard_layout();
}
storage_file_close(layout_file);
} else {
set_default_keyboard_layout();
}

storage_file_free(layout_file);
furi_record_close(RECORD_STORAGE);
}

// Convert ASCII to keycode using loaded layout (same as BADUSB_ASCII_TO_KEY)
uint16_t ascii_to_key(char c) {
if(!layout_loaded) {
set_default_keyboard_layout();
}

uint8_t ascii_code = (uint8_t)c;
if(ascii_code < 128) {
return keyboard_layout[ascii_code];
}
return HID_KEYBOARD_NONE;
}

void initialize_hid(void) {
// Stop other USB modes that might be active
Expand All @@ -11,16 +111,50 @@ void initialize_hid(void) {
furi_delay_ms(100);
}

void deinitialize_hid(void) {
// Release all keys before switching modes
release_all_keys();

// Stop HID mode and unlock USB
furi_hal_usb_unlock();

// Give system time to enumerate device
furi_delay_ms(100);
}

void deinitialize_hid_with_restore(FuriHalUsbInterface* previous_config) {
// Release all keys before switching modes
release_all_keys();

// Small delay to ensure all keys are released
furi_delay_ms(25);

// Restore previous USB configuration if provided
if(previous_config) {
furi_hal_usb_set_config(previous_config, NULL);
} else {
// Fallback to unlock if no previous config
furi_hal_usb_unlock();
}

// Give system time to enumerate device
furi_delay_ms(100);
}

// Release all keys (important after sending key combinations)
void release_all_keys(void) {
furi_hal_hid_kb_release_all();
}

// Type a string
// Type a string using loaded keyboard layout
void type_string(const char* str) {
for(size_t i = 0; i < strlen(str); i++) {
furi_hal_hid_kb_press(HID_ASCII_TO_KEY(str[i]));
furi_hal_hid_kb_release(HID_ASCII_TO_KEY(str[i]));
uint16_t keycode = ascii_to_key(str[i]);
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_delay_ms(10);
furi_hal_hid_kb_release(keycode);
}
furi_delay_ms(10); // Small delay between keypresses
}
}
Expand Down
6 changes: 5 additions & 1 deletion badusb/badusb.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include <usb_hid.h>

void initialize_hid(void);
void deinitialize_hid(void);
void deinitialize_hid_with_restore(FuriHalUsbInterface* previous_config);
void release_all_keys(void);
void type_string(const char* str);
void press_key(uint8_t key);
void press_key(uint8_t key);
void load_keyboard_layout(const char* layout_path);
uint16_t ascii_to_key(char c);
35 changes: 35 additions & 0 deletions ble/pm_ble_hid_ext_profile.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "pm_ble_hid_ext_profile.h"

static FuriHalBleProfileBase* pm_ble_profile_hid_ext_start(FuriHalBleProfileParams profile_params) {
UNUSED(profile_params);
return ble_profile_hid->start(NULL);
}

static void pm_ble_profile_hid_ext_stop(FuriHalBleProfileBase* profile) {
ble_profile_hid->stop(profile);
}

static void pm_ble_profile_hid_ext_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) {
furi_check(config);
furi_check(profile_params);
PmBleProfileHidExtParams* params = profile_params;

// Base HID config
ble_profile_hid->get_gap_config(config, NULL);

// Name already set by HID base: "Control <device>"

// Ensure iOS-friendly settings
config->bonding_mode = params->bonding; // true
config->pairing_method = params->pairing; // GapPairingPinCodeVerifyYesNo
}

static const FuriHalBleProfileTemplate pm_profile_callbacks = {
.start = pm_ble_profile_hid_ext_start,
.stop = pm_ble_profile_hid_ext_stop,
.get_gap_config = pm_ble_profile_hid_ext_get_config,
};

const FuriHalBleProfileTemplate* pm_ble_profile_hid_ext = &pm_profile_callbacks;


16 changes: 16 additions & 0 deletions ble/pm_ble_hid_ext_profile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <furi.h>
#include <furi_hal_bt.h>
#include <extra_profiles/hid_profile.h>
#include <bt/bt_service/bt.h>
#include <bt/bt_service/bt_i.h>

typedef struct {
bool bonding;
GapPairing pairing;
} PmBleProfileHidExtParams;

extern const FuriHalBleProfileTemplate* pm_ble_profile_hid_ext;


Binary file removed dist/debug/password_manager_d.elf
Binary file not shown.
Binary file removed dist/password_manager.fap
Binary file not shown.
Binary file removed img/01-menu.png
Binary file not shown.
Binary file removed img/02-saved.png
Binary file not shown.
Binary file removed img/03-insert.png
Binary file not shown.
Binary file removed img/04-insert.png
Binary file not shown.
Binary file removed img/05-insert.png
Binary file not shown.
Binary file removed img/06-delete.png
Binary file not shown.
Binary file removed img/07-delete.png
Binary file not shown.
Binary file modified img/add.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/ble.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/delete.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file renamed img/passwords.gif → img/keyboard.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/login.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/pcode.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading