I've always been a paranoid person around computers, and I've wanted to start using a password manager for a while, but the only cloud-based one which seemed trustworthy (1Password) costs money and is not open source.

For a long time, my compromise was a folder full of text files which required elevated permissions to read. Not a bad solution, but it gets messy across multiple devices.

I really wanted a cloud solution, but I didn't want to have to trust a provider... So I built my own!

https://pwm.sequentialread.com

The problem is relatively simple: I wanted to create a key, encrypt my files with it, and upload them somewhere. It needed to be secure, convenient, and always-on. (At work, folks seem to call it "Highly Available").

I started out by adapting my existing Golang/AngularJS web application template to the task. But I starting thinking,

How can I prove to a technical person that my code does what I say it does?

This is the central question which drove me to burn the midnight (and 3am) oil. In the end, I decided to completely re-write the application so it would have zero dependencies.

Well, actually, there still is one dependency: sjcl.js, the Stanford Javascript Crypto Library. And that's a good thing: I would rather trust a well-established crypto library to implement things like hash-functions and ciphers for me. Plus, it's easy to integrity check: Either see if my copy of the file has been signed by the author, or if it matches an officially provided version of the file.

But I wanted to have zero dependencies to help remove the mystery of what the code does (more specifically, to severely limit the things it "could" do). For example, in my code, the bit which encrypts the data before sending it across the wire (cryptoService.encrypt, which is basically an alias for sjcl.encrypt) is right next to the bit that actually sends it (XMLHttpRequest.send()).

Yes, it's a bit wordy since it has an Amazon S3 request interceptor in the middle, but here it is:

        // Encrypt Content first
        if(content) {
          if(typeof content == "object") {
            content = JSON.stringify(content, 0, 2);
          }
          content = cryptoService.encrypt(content);
        }

        // AWS S3 request interceptor
        if(url.startsWith(s3InterceptorSymbol)) {
          var path = url.replace(s3InterceptorSymbol, '');
          var s3Request = awsClient.s3Request(method, awsS3BucketRegion, awsS3BucketName, path, content);
          headers = s3Request.headers;
          url = s3Request.endpointUri;
        }

        httpRequest.open(method, url);
        httpRequest.timeout = 2000;

        Object.keys(headers)
          .filter(key => key.toLowerCase() != 'host' && key.toLowerCase() != 'content-length')
          .forEach(key => httpRequest.setRequestHeader(key, headers[key]));
          
        if(content) {
          httpRequest.send(content);
        } else {
          httpRequest.send();
        }

This hand-made http request function is meant to strengthen the claim that everything the app sends out is encrypted with the key you chose.

As a matter of fact, httpRequest in that code is the only instance of XMLHttpRequest in the entire application. Sure, that doesn't prove anything in isolation: Maybe some other part of the code is maliciously leaking sensitive information into the request. But a cursory glance around awsClient and the calling code shows, that's not the case.

Since all the code is relatively easy-to-read, and there are under 1000 lines of it in total, I would argue that this application is easy to audit. I think that's a win. If you know a little bit about JavaScript and you trust the sjcl, you should be able to prove to yourself that the crypto works.

I enjoyed the challenge of writing the entire application without the familiar comforts of hefty libraries like Angular. Of course the code quality, maintainability, etc suffered. I don't know many people who enjoy working on web applications that have document.getElementById() and other manual DOM manipulations sprinkled everywhere.

But I had other requirements besides ease-of-audit. I also wanted it to be easy to use and always-on. Here's how I tackled those:

  • Easy to Use
  • Auto-generated passphrases look like strings of english words. Under the hood they are ~128 bit numbers encoded base-10000.
  • Secure
  • Timestamp + mouse-movement + SHA256-based entropy generator helps users pick a random secure pass phrase to base their key on.
  • AES encryption from sjcl.
  • Always On
  • Once you have loaded it, it will continue work on that computer even if my server shuts down. (HTML5 Application Cache)
  • It stores its encrypted files in multiple places including Amazon S3, and S3 access works even if my server shuts off.

Getting the S3 HTTP request to work without pulling in an AWS SDK was an exercise in extremely verbose and pedantic precision which truely deserves a publication of its own, and will probably get one in the following weeks.

Edit: no, that never happened, and probably never will. Screw Amazon, even if their authentication layer is pretty cool.

I wasn't able to find any simple example code that implements AWS Signature Version 4 in JavaScript so I ended up porting/adapting their C# code example for the browser. I may publish it as a package later since the only other JavaScript one I was able to find uses Node.js libraries and hence is not very browser-friendly.

Finally, if you want to learn more check out the repository.

It's also on Docker Hub! although it should be easy enough to build yourself.

Comments