Skip to main content

Cardputer as a Hardware Password Manager

For the past month, I've been prototyping my own hardware password manager using the Cardputer, a compact device that's surprisingly perfect for the task. 


I learned about the Cardputer while searching for the ideal hardware to build a password manager. It's essentially a microcontroller (the ESP32-S3) packed with a screen and a keyboard. What really sold me on it were these features:

  • Direct Interaction: I can interact directly with the device – typing my master password, searching for credentials, and confirming password entry, all on the Cardputer itself.
  • USB Keyboard Emulation: The device can seamlessly emulate a USB keyboard, allowing for both manual and automatic password entry into other devices.
  • Hardware-Accelerated Crypto: The ESP32-S3 boasts hardware acceleration for AES and SHA operations, crucial for secure password management.
  • Secure Element Integration: The Cardputer supports an optional ATECC608B secure element (available as an official accessory), providing additional cryptographic capabilities, secure key storage, and true random number generation.

Taming the Cardputer


The official development framework is ESP-IDF, but after some research I decide to use esp-hal. I took it as a nice opportunity to learn Rust and embedded systems.

My journey with esp-hal quickly turned into a hands-on crash course in embedded systems development. I wasn't just writing high-level application code; I was crafting drivers from the ground up.

This meant diving into the specifics of the Cardputer's hardware. I spent most of my time implementing drivers, not quite at the level of manipulating memory addresses directly, but still very much involved in the low-level details. This included tasks like scanning the keyboard's button matrix and translating physical presses into logical key events, as well as working with communication protocols like I2C and SPI to interact with peripherals.

Simultaneously, I delved into the world of cryptography, deepening my understanding of common algorithms and concepts like AES modes of operation, key derivation functions (KDFs), and secure hashing. This project became a fantastic opportunity to connect the theoretical aspects of cryptography with practical implementation.

This project also sparked an exhilarating, and at times challenging, adventure in learning and applying Rust. I deliberately chose the no_std and no_alloc path, embracing a truly bare-metal approach. This decision plunged me into the deep end of embedded Rust, where every byte of memory matters. It was both a blessing and a curse: while it demanded careful resource management, with everything either stack-allocated or statically allocated, it also provided unparalleled control and efficiency. Along the way, I encountered some of Rust's more… idiosyncratic features, wrestling with async function traits, navigating the intricacies of generic types, and occasionally resorting to seemingly superfluous .map(|x| x) closures to appease the borrow checker and its lifetime requirements.


Securing the Secrets

The prototype is functional. While the source code isn't ready for release yet, I'd like to share some of the key design decisions.

For data serialization, I chose FlatBuffers. Its zero-copy nature is particularly well-suited to the no_alloc environment of this project, minimizing memory overhead. The main database structure is inspired by KeePass. Note that the database is read-only on the device; entries cannot be added, modified, or removed.

The database is encrypted using AES-GCM-SIV. The encryption key is derived via HKDF, with the input keying material (IKM) composed of three distinct components:

  • User's Master Password: The output of PBKDF2 applied to the user's master password, using a randomly generated salt.
  • eFuse Key in ESP32S3: The result of HKDF-Expand using a pre-defined message, keyed by random bytes securely burned into an ESP32-S3 eFuse. This ties the encryption to the specific hardware.
  • ATECC608B Private Key: The output of HKDF using a pre-defined message, keyed by a shared secret. This secret is established through ECDH key exchange between the ECC key stored securely within the ATECC608B and an ephemeral key pair generated by the encryptor (e.g., a host computer or another device).
Furthermore, when either or both of the hardware-based secrets (eFuse and ATECC608B) are enabled, they also contribute to individual password encryption. A separate key is derived from these secrets, using the password's sequence number within the database as the IKM for HKDF. This approach, inspired by KeePass's in-memory protection, provides an additional layer of security, though its benefit in this specific embedded context might be less pronounced than in a traditional software environment.

Okay, I might have gone a little overboard with the encryption. The eFuse and ATECC608B key derivation steps are probably not strictly necessary. But hey, it was fun to build! And even though secure elements aren't magic security shields (they do have known vulnerabilities), they make it way harder for someone to steal your passwords.


Final Thoughts

The Cardputer has proven to be a surprisingly capable platform for this secure password manager project, though not without its limitations.


What I liked:

  • Affordability: The Cardputer's low cost is a major advantage, making this project accessible.
  • Well-Suited Hardware: The hardware feature set is almost ideal – providing all the necessary functionality (display, keyboard, secure element, connectivity) without unnecessary extras.
  • Small Attack Surface: The firmware footprint is relatively small (~300KB), minimizing the potential attack surface.
  • Fast Boot Time: The device boots up quickly, making it convenient to use.
  • Rust Ecosystem: The Rust ecosystem, with its package manager (cargo) and readily available crates, made development fast, easy, and enjoyable.

Areas for Improvement:

  • Performance Limitations: The ESP32-S3's processing speed is a bottleneck, particularly for computationally intensive operations like PBKDF2-SHA512, which takes approximately 5 seconds for 65536 rounds. On the other hand, the performance is more than enough after the unlocking
  • Documentation Gaps: The documentation for both the ESP32-S3 and ATECC608B could be improved. I encountered significant challenges setting up flash encryption, secure boot, and the undocumented KDF command in the ATECC608B.
  • ATECC608B-TNGTLS Limitations: The Cardputer uses the ATECC608B-TNGTLS variant, which, unfortunately, does not enforce I/O encryption. This means communication between the ESP32-S3 and the secure element is not as secure as it could be.
  • Known Vulnerabilities: Both the ESP32-S3 and ATECC608B have known vulnerabilities. While these don't necessarily render the device insecure, they highlight the importance of a strong master password as the primary defense.
  • Missing RTIC Support: The lack of RTIC support on the ESP32-S3 is a missed opportunity for learning and exploration.
  • RustCrypto Hardware Acceleration Limitations: RustCrypto's AES implementation can effectively leverage hardware acceleration when available, but that does not seems true for the HMAC, HKDF, and PBKDF2 implementations. I believe it's related to the dependency on the Clone trait.
  • ECC Algorithm Support: The ATECC608B focuses on ECDSA curves and algorithms. Support for Ed25519 would be nice.

Comments

Popular posts from this blog

Determine Perspective Lines With Off-page Vanishing Point

In perspective drawing, a vanishing point represents a group of parallel lines, in other words, a direction. For any point on the paper, if we want a line towards the same direction (in the 3d space), we simply draw a line through it and the vanishing point. But sometimes the vanishing point is too far away, such that it is outside the paper/canvas. In this example, we have a point P and two perspective lines L1 and L2. The vanishing point VP is naturally the intersection of L1 and L2. The task is to draw a line through P and VP, without having VP on the paper. I am aware of a few traditional solutions: 1. Use extra pieces of paper such that we can extend L1 and L2 until we see VP. 2. Draw everything in a smaller scale, such that we can see both P and VP on the paper. Draw the line and scale everything back. 3. Draw a perspective grid using the Brewer Method. #1 and #2 might be quite practical. #3 may not guarantee a solution, unless we can measure distances/p...

Qubes OS: First Impressions

A few days ago, while browsing security topics online, Qubes OS surfaced—whether via YouTube recommendations or search results, I can't recall precisely. Intrigued by its unique approach to security through compartmentalization, I delved into the documentation and watched some demos. My interest was piqued enough that I felt compelled to install it and give it a try firsthand. My overall first impression of Qubes OS is highly positive. Had I discovered it earlier, I might have reconsidered starting my hardware password manager project. Conceptually, Qubes OS is not much different from running a bunch of virtual machines simultaneously. However, its brilliance lies in the seamless desktop integration and the well-designed template system, making it far more user-friendly than a manual VM setup. I was particularly impressed by the concept of disposable VMs for temporary tasks and the clear separation of critical functions like networking (sys-net) and USB handling (sys-usb) into the...

Exploring Immutable Distros and Declarative Management

My current server setup, based on Debian Stable and Docker, has served me reliably for years. It's stable, familiar, and gets the job done. However, an intriguing article I revisited recently about Fedora CoreOS, rpm-ostree, and OSTree native containers sparked my curiosity and sent me down a rabbit hole exploring alternative approaches to system management. Could there be a better way? Core Goals & Requirements Before diving into new technologies, I wanted to define what "better" means for my use case: The base operating system must update automatically and reliably. Hosted services (applications) should be updatable either automatically or manually, depending on the service. Configuration and data files need to be easy to modify, and crucially, automatically tracked and backed up. Current Setup: Debian Stable + Docker My current infrastructure consists of several servers, all running Debian Stable. System Updates are andled automatically via unattended-upgrades. Se...