Buttercup - A NodeJS password manager

Perry Mitchell / 2016-10-23 17:15:02
Buttercup - A NodeJS password manager

After sev­eral at­tempts at build­ing a pass­word man­ager I liked in C#, I switched to NodeJS as the plat­form of choice for Buttercup. The Node ap­pli­ca­tion was de­signed to cover sev­eral points that were the rea­son I wanted out of us­ing plat­forms like LastPass, KeePass etc.:

  • I want to host the archive my­self, wher­ever I want. It should be ac­ces­si­ble from all of my de­vices.
  • I don’t want to pay for an ac­count or host­ing.
  • I want the same ap­pli­ca­tion ex­pe­ri­ence on every plat­form.
  • I want save-con­flict res­o­lu­tion sup­port, if I for­get to close and save changes on an­other de­vice.
  • I want strong en­cryp­tion but a light­weight file size.

Sallar and my­self started build­ing Buttercup in September 2015 and since then I feel we can say that we’ve ad­dressed al­most all of those points (except for the every plat­form bit - that’s work-in-progress). There’s so much go­ing on, but it def­i­nitely feels like we’re go­ing in the right di­rec­tion. It’s still early days, but our in­tended of­fer­ing is go­ing to be very broad.

Components

Buttercup, the sys­tem, is made up of sev­eral com­po­nents (repos­i­to­ries and npm pack­ages):

  • The core - Responsible for the archive struc­ture, func­tion­al­ity, en­cryp­tion, sav­ing and load­ing. It man­ages the con­flict res­o­lu­tion and delta his­tory flat­ten­ing for archive op­ti­mi­sa­tion.
  • The desk­top GUI - The beau­ti­ful user in­ter­face for PCs run­ning Windows, Mac or Linux. Built on elec­tron, it gives us com­plete con­trol over its be­hav­iour and de­sign while re­main­ing highly portable.
  • The core, for the web - The core li­brary, com­piled and mini­fied for use in browsers.
  • Browser plu­g­ins - Currently only an ex­ten­sion for Chrome is in the works, but Firefox and Safari will also be among the ear­li­est sup­ported browsers.
  • The CLI - A com­mand-line ap­pli­ca­tion for in­ter­act­ing with archives in the ter­mi­nal.
  • The server - A server ap­pli­ca­tion de­signed to host and pro­vide an in­ter­face for user ac­counts and archives.

Buttercup flier

Buttercup desk­top ap­pli­ca­tion

The desk­top ap­pli­ca­tion was built with Electron and makes use of the core li­brary. It pro­vides an in­ter­face for users to in­ter­act with their archives, no mat­ter where they’re stored. Each re­mote in­ter­face (cur­rently only files in the first ver­sion) will be sup­ported on every plat­form pos­si­ble.

As work con­tin­ues on the next ver­sion of the GUI, the first ver­sion is quite com­plete and shows the sim­plic­ity of work­ing with our pass­word man­ager:

Buttercup GUI v1

Buttercup core li­brary

The core li­brary is re­spon­si­ble for the se­cure archive han­dling func­tion­al­ity used by all other ap­pli­ca­tions.

const Buttercup = require("buttercup");

The in­ter­nals of the li­brary are so­phis­ti­cated and com­plex at times, but the ex­ter­nal API re­mains quite sim­ple and easy to grasp.

Buttercup pro­vides an Archive ob­ject which rep­re­sents ex­actly what you think it does - a pass­word archive, run­ning in mem­ory.

const { Archive } = Buttercup;

let myArchive = new Archive(),
    myGroup = myArchive.createGroup("My group");

An archive con­tains groups and en­tries to help or­gan­ise cre­den­tials. Groups can be nested, and en­tries are stored in groups. An Entry rep­re­sents a lo­gin or cre­den­tial for a sys­tem.

let myLogin = myGroup.createEntry("Some website");
myLogin
    .setProperty("username", "person123")
    .setProperty("password", "xyz123$")
    .setMeta("url", "http://website.com/login.php");

Archives can be saved to a des­ti­na­tion us­ing a Datasource. The ba­sic ones are TextDatasource (output to a string) and FileDatasource (output to a lo­cal file), but there are also re­mote ones like OwnCloudDatasource and ButtercupServerDatasource. One might want to save an archive to their OwnCloud file host­ing, for ex­am­ple.

const { OwnCloudDatasource } = Buttercup;

let datasource = new OwnCloudDatasource(
    "https://my-server.org",
    "/security/buttercup-archive.bcup",
    "ownCloudUsername",
    "pass12345"
);

// `.save` returns a Promise
datasource.save(myArchive, "myMasterPassword!");

Buttercup keeps a his­tory of com­mands run against the archive each time a change is made:

cmm "Buttercup archive created (2016-10-22)"
fmt "buttercup/a"
cgr 0 a021e761-a8e2-4344-8901-cd9719f8e90d
tgr a021e761-a8e2-4344-8901-cd9719f8e90d "test-group-main"
pad 75ec23b6-2501-4a60-aed6-e52851f63514
cen a021e761-a8e2-4344-8901-cd9719f8e90d 7b88ff33-16d6-47ee-bac2-92ec8673b29f
sep 7b88ff33-16d6-47ee-bac2-92ec8673b29f title "test-entry-main"
pad c15b15c2-e265-4be4-83ef-875a1a2b11f6
sep 7b88ff33-16d6-47ee-bac2-92ec8673b29f username "user123\@test.@D"
pad 4d1a0ecf-ce41-4f21-8bbd-e27a32f44981
sep 7b88ff33-16d6-47ee-bac2-92ec8673b29f password "* ª¾¸“¯¼¾°Í¡! "
pad aff747d7-3131-4322-a79b-69cc5458036c
sem 7b88ff33-16d6-47ee-bac2-92ec8673b29f "test-meta" "test-value 8"
pad fb9b6251-2c87-4d80-84b1-6a97e28c7e79

Buttercup recog­nises these com­mands and makes mod­i­fi­ca­tions to the ob­ject in mem­ory. When sav­ing, the core en­crypts and gzips the his­tory be­fore stor­ing it us­ing a Datasource. These com­mands al­lows Buttercup to mit­i­gate con­flicts when sav­ing buy find­ing a com­mon base be­tween two con­flict­ing archive his­to­ries and zip­ping them to­gether from that point on­wards. That means that if two in­stances of the same archive are left open and saved at the same time, the dif­fer­ences be­tween them are not lost. Deletion ac­tions are can­celled in the case of a merge, but this al­lows the user to re­tain all of their cre­den­tial in­for­ma­tion.

Because jug­gling an archive and its data­source while try­ing to per­form con­flict de­tec­tion and merg­ing could be con­sid­ered a com­pli­cated task, there’s a handy class called Workspace that’s de­signed to make this process eas­ier.

const { Workspace } = Buttercup;

let workspace = new Workspace();
workspace
    .setArchive(myArchive)
    .setDatasource(datasource)
    .setPassword(masterPassword);

workspace
    .archiveDiffersFromDatasource()
    .then(function(differs) {
        if (differs) {
            return workspace.mergeFromDatasource();
        }
    })
    .then(function(mergedArchive) {
        // merged!
        // at this point we could save it safely back to the datasource
    });

Under the hood

Buttercup uses stan­dard AES CBC en­cryp­tion with 256bit keys, salts, and a SHA-256 HMAC for au­then­ti­ca­tion. It en­crypts and de­crypts text only, and uses GZip to com­press the his­tory be­fore en­cryp­tion.

The en­cryp­tion that Buttercup uses was strong enough that it war­ranted its own repos­i­tory and iden­tity, and it now re­sides in a pack­age called io­cane. Its sole job is en­cryp­tion and de­cryp­tion us­ing proven, strong meth­ods that have a lengthy field record - like AES CBC.

The keys used for en­cryp­tion are gen­er­ated us­ing the PBKDF2 al­go­rithm with 10s/100s of thou­sands of it­er­a­tions. The core-web li­brary also uses the same al­go­rithm for key gen­er­a­tion in io­cane to re­main com­pat­i­ble with the PC ver­sion. To keep up in terms of speed, core-web patches the PBKDF2 func­tion­al­ity in the browser with that of the SubtleCrypto in­ter­face.

As io­cane uses mostly Node’s built-in en­cryp­tion meth­ods for its AES en­cryp­tion, there’s no hope of re­triev­ing the data within an archive if the mas­ter pass­word is for­got­ten.

Storage

Buttercup out­puts .bcup files when us­ing a Datasource other than TextDatasource. These bcup files are text files and take up lit­tle space (even with a hefty his­tory - gzip com­pres­sion of re­peat­ing IDs is good!). They’re safe to store on your PC, but should never be care­lessly shared with any­one.

Usability

Buttercup was de­signed pri­mar­ily as a pass­word man­ager - one which would be used in graph­i­cal en­vi­ron­ments for au­then­ti­cat­ing users in a va­ri­ety of en­vi­ron­ments. That’s not to say that this is its only use, how­ever…

Buttercup pro­vides an in­ter­face to store a tree of data with con­fig­urable node struc­tures in a se­cure man­ner. With lit­tle work, any sort of in­ter­face could be writ­ten over the top of Buttercup to pro­vide a se­cure data store for many use cases.

git-la­bel­maker ac­tu­ally uses Buttercup to fetch and store user to­kens for in­ter­act­ing with Github.

Whether it’s as a back­end to some ap­pli­ca­tion or a sup­port­ing mech­a­nism for a phys­i­cal de­vice, Buttercup can be made to store and re­trieve any kind of sen­si­tive data.