Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample vault reading code in README.md does not work as suggested in NodeJS. #326

Open
thaining opened this issue Mar 8, 2023 · 1 comment

Comments

@thaining
Copy link

thaining commented Mar 8, 2023

Code in the README.md to read Vault content does not work as suggested in NodeJS.

The README suggests that following code sample should work:

const { Credentials, FileDatasource, Vault, init } = require("buttercup");

init();

const datasourceCredentials = Credentials.fromDatasource({
    path: "./user.bcup"
}, "masterPassword!");
const fileDatasource = new FileDatasource(datasourceCredentials);

fileDatasource
    .load(datasourceCredentials)
    .then(Vault.createFromHistory)
    .then(vault => {
        // ...
    });

This hits two problems. The first problem is with the integrity of the datasourceCredentials. Running the above snippet with node and a valid buttercup file produces the following error during load():

/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/datasources/TextDatasource.js:142
            return Promise.reject(new Error("Provided credentials don't allow vault decryption"));
                                  ^

Error: Provided credentials don't allow vault decryption
    at FileDatasource.load (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/datasources/TextDatasource.js:142:35)
    at /home/thaining/tmp/builder/creator/node_modules/buttercup/dist/datasources/FileDatasource.js:103:30

This appears to be cause of imprecise use of copy-by-sharing in the TextDatasource constructor:

    constructor(credentials: Credentials) {
        super();
        this._credentials = credentials;
        this._credentials.restrictPurposes([Credentials.PURPOSE_SECURE_EXPORT]);
        this._content = "";

restrictPurposes() modifies an object called by reference, not a private copy. The object only has a 'secure-export' purpose, which causes fileDatasource.load() to fail.

An ugly workaround is to create another copy of the object.

import { Credentials, FileDatasource, Vault, init } from "buttercup";

init();

const datasourceCredentials = Credentials.fromDatasource({
    path: "./user.bcup"
}, "masterPassword!");
const loadCredentials = Credentials.fromDatasource({
    path: "./user.bcup"
}, "masterPassword!");
const fileDatasource = new FileDatasource(datasourceCredentials);

fileDatasource
    .load(loadCredentials)
    .then(Vault.createFromHistory)
    .then(vault => {
        // ...
    });

This reveals a second error:

/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:375
            throw new Error(`Invalid command: ${command}`);
                  ^

Error: Invalid command: [object Object]
    at VaultFormatA._executeCommand (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:375:19)
    at /home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:208:42
    at Array.forEach (<anonymous>)
    at VaultFormatA.execute (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:208:18)
    at Function.createFromHistory (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/core/Vault.js:30:22)
    at /home/thaining/tmp/builder/creator/reader.js:18:22

This occurs because of how TextDataSource.load() eventually returns the Vault() history:

        const Format = detectFormat(this._content);
        return Format.parseEncrypted(this._content, credentials).then((history: History) => ({
            Format,
            history
        }));

It's returning an anonymous object, not a DatasourceLoadedData. The compiled javascript sees this as single returned object with history and Format members. The undefined Format member passed back by load() is tolerated by createFromHistory(), but the object passed in is not a valid History. Execution fails as soon as that non-History object is used.

    static createFromHistory(history: History, format: any = getDefaultFormat()): Vault {
        const vault = new Vault(format);
        vault.format.erase();
        vault.format.execute(history);

vault.format in my case resolves to VaultFormatA, and _executeCommand() does not know what to do with it.

    execute(commandOrCommands: string | Array<string>) {
        if (this.readOnly) {
            throw new Error("Format is in read-only mode");
        }
        const commands = Array.isArray(commandOrCommands) ? commandOrCommands : [commandOrCommands];
        commands.forEach(command => this._executeCommand(command));

I'm not much of a typescript writer, but I tried compiling a typescript version of the snippet. The compiler recognized the same problem immediately:

reader.ts:12:11 - error TS2345: Argument of type '(history: History, format?: any) => Vault' is not assignable to parameter of type '(value: DatasourceLoadedData) => Vault | PromiseLike<Vault>'.
  Types of parameters 'history' and 'value' are incompatible.
    Type 'DatasourceLoadedData' is missing the following properties from type 'History': length, pop, push, concat, and 16 more.

12     .then(Vault.createFromHistory)

Doing this resolves the issue for NodeJS:

fileDatasource
    .load(fooDatasourceCredentials)
    .then(datasourceLoadedData => {
        return Vault.createFromHistory(datasourceLoadedData.history,
                datasourceLoadedData.Format);
    })
    .then(vault => {
@fkasler
Copy link

fkasler commented Jun 22, 2023

Check out this dump utility. I'm sure you can tweak the logic for your needs. Buttercup 'a' format is a little messy, but 'b' format is just nested JSON: https://github.com/fkasler/butterbrute/blob/main/dump_vault.mjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants