I’ve been using an OpenBSD laptop as my workstation a lot more lately, probably because most of my hardware just works now and I don’t have to think too much about it. The touchpad works when I touch it, I can be confident that when I close the lid, the laptop will fully suspend and then fully resume again when I open it, WiFi works all throughout my house (although it’s not terribly fast), and my web browser is fast and stable. What amazing times we live in.
In the past, one thing that frequently kept me going back to my Mac, aside from iOS and Android development, was 1Password. I have a ton of logins for websites and servers, and because my browsers are all configured to clear cookies for most websites after I close their tabs, I need frequent access to passwords synced across my laptops and phones, and 1Password has great apps for all of those except OpenBSD.
All of 1Password’s syncing currently works through my Dropbox account. My Mac has 1Password configured to store its encrypted database in my local Dropbox directory, Dropbox does its automatic syncing of that directory to their servers, and 1Password on my phone uses Dropbox’s API to pick up any changed files. It’s been reliable for years, I have local versioned backups of my database, it works when my devices are offline, and I know I can access that data years in the future.
Using 1Password on OpenBSD
To bring OpenBSD into the mix, there are 3rd-party command-line apps which can read local 1Password files such as 1pass written in Go. Getting the 1Password files onto OpenBSD was left as an exercise to the reader, sending me down the rabbit hole of trying to add OpenBSD support to various FUSE-based packages that could provide a local filesystem view of my Dropbox directory. I managed to hack one of them into semi-working shape, but eventually I gave up and used rclone to do one-way fetching of my Dropbox directory on demand.
While this allowed me to at least view and copy passwords, the process was less
Browsing in Firefox, I’d have to open a terminal, type
1pass copy <some website>,
choose the right one,
and then go back to Firefox and paste it in the proper field.
Tedious, error-prone, vulnerable to phishing, and now my password is hanging out
in clear-text on the clipboard.
A Firefox add-on called Passcards from the developer of 1pass seemed encouraging, as it did Dropbox syncing on its own and supported auto-filling passwords in the browser, but I could never get it to work. The hard-coded Dropbox API token in the add-on doesn’t work and the mess of Node dependencies to build a local version failed miserably on OpenBSD.
Meanwhile, AgileBits, the 80-person company developing 1Password, has been pushing their new hosted, subscription-based model for 1Password going forward. Instead of users being in control of their data files, 1Password will store them on AgileBits’ servers and users pay a monthly subscription fee for the privilege, forever.
I’m an app developer, I get it. A big company can’t sustain development of a product that users only pay for once. However, I’ve paid for 1Password and all of its major version upgrades, and the $10 or whatever it was to unlock the “pro” features of the iOS app. I’m not opposed to paying money for apps, or for upgrades, or even for a subscription, but I don’t want to pay to host my passwords on AgileBits’ servers. Security concerns aside, there is an issue of lock-in and now having to make my OpenBSD hacks work with AgileBits’ new API (is there even one?) instead of just accessing and backing up files from Dropbox.
Since I wasn’t sure how long 1Password would keep working with its non-subscription-based syncing and I was still missing first-class OpenBSD support, I started looked into migrating to something else.
The main competitor to 1Password is LastPass, which looks nice and works well as a standalone Firefox add-on on OpenBSD, but it has the same lock-in problem and server-side security concerns as 1Password.
KeePass is a popular open-source alternative but its use case seems focused on a single machine. I don’t need a stand-alone GUI and I do need browser extensions and mobile apps that can all sync reliably. And honestly, looking at their plugins page left me with a bit of decision fatigue: which ones are good, which ones are secure, which ones are still maintained? Does the browser extension have to read files from my home directory or talk to a daemon that my unprivileged Firefox won’t be able to do?
There are various command-line concoctions such as pass which stores PGP-encrypted files in a Git repo, but that doesn’t improve my situation over 1Password. I would still have to manually look up passwords and copy them to the clipboard. These command-line packages also lack mobile apps and syncing.
Eventually I stumbled upon Bitwarden which is similar to LastPass but is entirely open-source and its primary developer is funded by users paying for subscriptions to store their data on Bitwarden’s servers. However, all of their browser extensions and phone apps support setting a custom API URL before logging in, to allow for private installations. The iOS app and Firefox extension that I tried out looked fairly well polished, but I was more concerned with it being an open platform so I could fix bugs, add features, and host my own data.
Unfortunately, the open-source backend for these apps is written in .NET and expects to talk to a Microsoft SQL Server, requiring a big Docker image to deploy a private installation on Linux.
Since I was expecting to run my own API server on OpenBSD without all of that overhead, I decided to write my own compatible server. Sadly, there is no documentation on Bitwarden’s API (outside of its .NET code) so I was not even able to figure out what my server would need to provide.
Rather than wade through lots of .NET code, I decided to go for a black-box approach. I wrote a simple proxy in Sinatra that I could point the Bitwarden Firefox add-on to as its private API URL. The proxy would intercept each request, print it out to the console, then send it to Bitwarden’s actual API, print out the response, and send it back to the Firefox add-on.
With my documentation in-hand, I wrote a new Sinatra server that implements all of the API calls needed by the Firefox extension and iOS app. I deployed it to a server with Unicorn behind nginx, and used Let’s Encrypt to get a TLS certificate for it.
My API server is now small and easy to understand, it has a much smaller attack
surface than the .NET version, and all of my data is stored in a SQLite database
that I can backup and version with
No lock-in, a first-class experience on OpenBSD and Firefox, and I feel better
understanding the details of how my data is encrypted.
Migrating from 1Password
The Bitwarden web client (not the Firefox add-on) supports directly importing 1Password data files for users subcribed to Bitwarden’s hosted service. Since I’m not using Bitwarden’s web client, I wrote a command-line 1Password conversion tool that can read a 1Password Interchange Format file, encrypt the passwords using Bitwarden’s format, and insert them into the database that bitwarden-ruby uses.
After importing more than 700 logins from my 1Password file, I noticed that the Bitwarden Firefox add-on was quite sluggish on OpenBSD. Unlocking it with my master password would take four or five seconds to parse everything before showing the large list of logins.
Since this data was years of migrated 1Password installations and other password stores, I decided to spend a few hours cleaning it up. After deleting some 300 logins and moving others into various folders, the add-on seems a bit snappier though still leaves something to be desired. I’d also like to change its keyboard shortcut to Alt+\ like Cmd+\ is for 1Password, but Firefox’s new WebExtension system doesn’t support changing these hard-coded keyboard shortcuts yet like Chrome does.
At this point I’ve been using Bitwarden’s iOS app and Firefox extension exclusively.
My server now has TOTP support, and everything seems to be working well.
Fetch the Rubywarden code from GitHub if you want to check it out.