Nostrkey - a program for nostr identities with hardware protection

❝Nostrkey is a program for TKey that provides functionality for hardware-protected nostr identities.❞
Contents

Nostrkey is a small TKey-program and demonstration client that offers three levels of protection for nostr-identities supported by hardware. Each level has its own benefits and drawbacks. Nostrkey, as a project, additionally serves as a small code-base “spin-off” from a larger project and proving-ground.

What is a TKey?

TKey is a novel kind of security device. It offers an internal secret, a very minimal firmware, and an ability to load small arbitrary programs that each receive their own derivation of this (inaccessible) internal secret. The secret is derived using BLAKE2 from internal secret, program-binary and the passphrase used when loading the program. The result is a program loaded into a security device with an unpredictable secret, allowing it to independently perform computations, working with its own secret and true-randomness. TKey offers limited capabilities of interacting with the user through touch-sensor and LED. TKey contains a general-purpose RISC-V CPU with limited feature-set. This limited support has turned out to be an advantage, given a recent attack on some RISC-V processors through their vector-extension.

TKey, with its inaccessible internal secret, its ability to load arbitrary programs, and small firmware provides a rather unique device for security solutions that have a need for secure processing. The fact that any change in the bytes of the program-binary results in different secret bytes, means that even though a program-binary is stored on accessible storage, there is no benefit in making changes in order to manipulate the device into exposing its secrets: you would be revealing different secrets. This reduces the trust-problem, at least for the program-binary, to a matter of trust-on-first-use (TOFU).

What is nostr?

Nostr, and the nostr protocol, provide a decentralized mechanism for social media. At the core is the idea of “events” being submitted to “relays” which then (store and) forward the events to whichever clients is subscribed to a matching pattern.

The notion of an identity or “account” is the possession of a cryptographic keypair. Anyone who can successfully sign events for a particular public key, essentially acts as the identity (“person”) behind this public key. Relays, the nostr-servers that accept submitted events and store and relay them to clients, verify an event’s authenticity by verifying that a submitted event is properly signed (and the signature successfully verifies).

Now, what is nostrkey

Nostrkey is a TKey program that uses TKey’s capabilities to provide nostr-identities. Identities are offered at three levels of protection, each with their own benefits and drawbacks. Furthermore, caching is provided such that repeated use are sped up. Interactions with nostrkey are end-to-end encrypted, such that only the nostrkey-program and the client know what is sent.

At highest level, identities are derived from the derived internal secret. These identities, really just an elliptic curve keypair, cannot be exported: they exist only on that specific device. The second level offers the ability to send any arbitrary identity to nostrkey to seal for on-device signing. The user must make a backup before sending the identity secret, and the sealed identity cannot be exported. Cryptographic signing is performed on the device. The third level seals an identity to protect it when stored. Whenever the sealed identity is sent to nostrkey, nostrkey will just decrypt it. This makes it possible to securely store the identity because the device is a necessary component for decryption, while a nostr client can load the original keypair in memory for fast and efficient use. This third level assumes that clients are trustworthy, but assists in storing the identity securely.

In all of this, it needs to be considered that the program-binary cannot arbitrarily be changed or updated. Changing the program would require identities to be reimported and “highest-level” identities, i.e. those deterministically generated by nostrkey itself, would be lost due to the change of secret. (You can, of course, always load the other version of the program.) This provides significant benefits to security, but also requires care when making a choice. For that reason, there is significance in each of the three levels of protection.

Proving-ground

Apart from the obvious uses of nostrkey, it also functions as a proving-ground of sorts. nostrkey uses libraries and my own work on a code-base and some conversions. Nostrkey is a smaller and more direct use-case for these components, so it allows me to test and verify my work early, and thoroughly in a real-life use-case with clear goal and benefits.

Nostrkey provided me with an opportunity, among others, to test the general struture of the code-base and design; end-to-end encryption; the adaptations to the original frame-protocol for the TKey (allows sending larger frames by virtue of marking a frame as a “continutation); conversion of p256-m (the original is also used in Tillitis’s TKey-FIDO) to Koblitz-curve p256k1; implementation of Schnorr-signatures; general use of TKey-hardware, its benefits and drawbacks; ability to correct for errors during interactions with the device; experiment with and use of various security features offered by TKey such as the CPU execution monitor; a good way to set up a rather minimal secure communication mechanism; trial-run a Go GUI framework ‘Fyne’ for the desktop-tool that can be used for sealing nostr keys; among others.

Hopefully, in the future this will also prove to be useful as a smaller, more approachable code-base when looking in more depth into possibilities of hacking nostrkey through malicious input. Although I suspect that the surface of this in nostrkey is rather minimal as it works almost exclusively with fixed-size user inputs that are treated as raw bytes. This is in part by design. The interesting aspect of the TKey is that modifying the binary beforehand is not an option, as you would lose access to the root secret of the program, so attacks need to rely on other kinds of vulnerabilities and flaws. It may also evolve when improvements to cryptography are available/possible.

And besides all of the aspects listed above, it also proved interesting to see how TKey as a device – a small embedded general-purpose CPU – would perform. In this particular case, for handling keypairs and signing messages. Signing messages, with some precomputation that is beneficial for repeated use, takes ~9 seconds. Which isn’t super-fast, but still very reasonable for plenty of use-cases. Data-transfer is also a relevant factor, especially with increasing sizes, although my impression is that many security use-cases, whenever sizes become sufficiently large, hashes are used or necessary instead.

Nostrkey client-UI

In addition, a small tool is provided. The client-program: a GUI-program that provides the bare-minimum function for submitting a nostr-event signed with nostrkey (mainly useful for testing). More importantly, it is a stand-alone tool that can be used to interact with a nostrkey to import (“seal”) a nostr identity (“nsec”), either for use for on-device signing or to unseal for use inside a nostr client. This client is written in Go and the Fyne UI-framework (github).

nostrkey-client-UI

The tool is simple but sufficient to provide access to nostrkey functionality. The hexadecimal identifier in the top is the public key of this particular nostrkey. This should be the same every time one connects to the same hardware with the same program-binary. This key is verified when connecting: the device claims to own this keypair and proved it owned the keypair. The device-status indicator shows whether the program-binary is loaded and/or an operation is pending. Account are for (pre)loading an internal account, i.e. highest level protection. The “tools”-controls below seal/load or seal/unseal a provided (encoded) key, i.e. the lower levels of protection offered.

As part of the sealing process, additional data is added to indicate whether this key is sealed for loading or unsealing. Only if the same choice is made, will nostrkey be able to decrypt the sealed key for use.

Future

Future additions will depend largely on how the Nostr ecosystem evolves. There are a few proposals that show promise to implement in a client such as this. Such as to delegate permissions to other keypairs, which would allow for nostrkey-identity to be a “master-key”, with delegation to other clients for daily use, e.g. your Android-client. Another option is a small Android-app for satisfying signing-requests, effectively bridging the client to nostrkey.

There are a few interesting possibilities. There is, however, the original, more comprehensive project from which this “spin-off” originated. It has benefited a lot from the experience gained from nostrkey as a proving-ground and will be the focus for this moment.