node.jstypescriptkeepass

kdbxweb usage for creating dbs, storing and retrieving passwords for use in scripts/jobs


I'm trying and failing at learning to use this kdbxweb library. The documentation for it is confusing me, probably because I lack some prerequisite knowledge that is standard and so the documentation isn't really written for me yet.

Below is my code where I'm trying to learn to use it. All I really want to use this for is a place to store passwords rather than in plain text in a way I can send script to a team member and they can setup a similar credentials database either within the script or outside it and it will pull in their various ODBC database passwords.

The idea eventually would be to create the entry name as the name of the given ODBC connection and then based on a request to initiate connection the UID and PWD would be retrieved and added into connection string. I'm trying to get away from MS Access/VBA for this sort of thing and learn to use NodeJS/TypeScript for it instead.

import * as fs from 'fs';
import * as kdbx from 'kdbxweb';

(async() => { 
  try {
    const database = kdbx.Kdbx.create(new kdbx.Credentials(kdbx.ProtectedValue.fromString('test')),'credentials');
    //const group = database.createGroup(database.getDefaultGroup(),'subgroup');
    //const entry = database.createEntry(group);
    //entry.fields.set('Password',kdbx.ProtectedValue.fromString('test'));
    //entry.pushHistory();
    //entry.times.update();
    await database.save();
    //fs.writeFileSync('credentials/credentials.kdbx',data);
  } catch (e :any) {
    throw e;
  }
})();

The error I'm getting when trying to do this is "argon2 not implemented" and while argon2 is mentioned at the top of documentation, I don't understand what that is even talking about in the least. It sounded like it has to do with an additional cryptography API that I don't think I even should need. I tried to take the code of the example implementation but I had no idea how to actually make use of that at all.

I also tried reading code for the web-app written using this library, but the way it's integrated into the application makes it completely impossible for me to parse at this point. I can't tell what type of objects are being passed around/etc. to trace the flow of information.


Solution

  • Old solution below

    I found a better solution, it was painful to learn how to do this, but I did eventually get it working. I'm using the node C++ implementation of argon instead and it no longer echos the minified script into console

    This is setup as argon/argon2node.ts, and requires the argon2 node library. I think now that I got this working if I wanted to switch to the rust version or something like that I could probably work that out. It's mostly about figuring out exactly where the parameters need to go, since sometimes the names are a little different and you have to convert various parameters around.

    import { Argon2Type, Argon2Version } from "kdbxweb/dist/types/crypto/crypto-engine";
    import argon from 'argon2';
    
    export default async function argon2(
        password: ArrayBuffer,
        salt: ArrayBuffer,
        memory: number,
        iterations: number,
        length: number,
        parallelism: number,
        type: Argon2Type,
        version: Argon2Version
    ): Promise<ArrayBuffer> {
        try {
            //https://github.com/keeweb/kdbxweb/blob/master/test/test-support/argon2.ts - reviewed this and eventually figured out how to switch to the C++ implementation below after much pain 
            const hashArr = new Uint8Array(await argon.hash(
                Buffer.from(new Uint8Array(password)), {
                timeCost: iterations,
                memoryCost: memory,
                parallelism: parallelism,
                version: version,
                type: type,        
                salt: Buffer.from(new Uint8Array(salt)),
                raw: true
              }
            ));
            return Promise.resolve(hashArr);
        } catch (e) {
            return Promise.reject(e);
        }
    
    }
    
    

    And below is my odbc credentials lookup based on it

    import * as fs from 'fs';
    import * as kdbx from 'kdbxweb';
    import argon2 from './argon/argon2node';
    import * as byteUtils from 'kdbxweb/lib/utils/byte-utils';
    
    export default async(title : string) => {
      try {
        kdbx.CryptoEngine.setArgon2Impl(argon2);
        const readBuffer = byteUtils.arrayToBuffer(fs.readFileSync('./SQL/credentials/credentials.kdbx'));  
        const database = await kdbx.Kdbx.load(
          readBuffer,  
          new kdbx.Credentials(kdbx.ProtectedValue.fromString('CredentialsStorage1!'))
        );
    
        let result;
    
        database.getDefaultGroup().entries.forEach((e) => {
          if(e.fields.get('Title') === title) { 
            const password = (<kdbx.ProtectedValue>e.fields.get('Password')).getText();
            const user = <string>e.fields.get('UserName');
            result = `UID=${user};PWD=${password}`;
            return;      
          }
        });
    
        return result;
      } catch(e : any) {
        throw e;
      }
    }
    

    To resolve this, I had to do a bit of reading to understand buffers and arraybuffers and such from the documentation a bit, which wasn't easy but I eventually figured it out and created below testing reading and writing entries and such. I still have a bit to learn but this is close enough I thought it worth sharing for anyone else who may try to use this

    I also had to get a copy of argon2-asm.min.js and argon2.ts which I pulled from the github for keeweb which is built with this library.

    import * as fs from 'fs';
    import * as kdbx from 'kdbxweb';
    import { argon2 } from './argon/argon2';
    
    function toArrayBuffer(buffer : Buffer) {
      return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
    }
    
    function toBuffer(byteArray : ArrayBuffer) {
      return Buffer.from(byteArray);
    }
    
    (async() => { 
      try {
        kdbx.CryptoEngine.setArgon2Impl(argon2);
        fs.unlinkSync('./SQL/credentials/credentials.kdbx');
        const database = kdbx.Kdbx.create(new kdbx.Credentials(kdbx.ProtectedValue.fromString('test')),'credentials');    
        const entry = database.createEntry(database.getDefaultGroup());
        entry.fields.set('Title','odbc');
        entry.fields.set('Password',kdbx.ProtectedValue.fromString('test'));    
        const data = await database.save();
        fs.writeFileSync('./SQL/credentials/credentials.kdbx',new DataView(data));
        const readData = toArrayBuffer(fs.readFileSync('./SQL/credentials/credentials.kdbx'));
        console.log('hithere');
    
        const read = await kdbx.Kdbx.load(
          readData,  
          new kdbx.Credentials(kdbx.ProtectedValue.fromString('test'))
        );
    
        console.log('bye');
        console.log(read.getDefaultGroup().entries[0].fields.get('Title'));    
        const protectedPass = <kdbx.ProtectedValue>read.getDefaultGroup().entries[0].fields.get('Password');
        console.log(
          new kdbx.ProtectedValue(
            protectedPass.value,
            protectedPass.salt
          ).getText()
        );
      } catch (e :any) {
        console.error(e);
        throw e;
      }
    })();
    
    

    Things that I don't grasp I'd like to understand better include why the argon implementation isn't built-in. He says " Due to complex calculations, you have to implement it manually " but this just seems odd. Perhaps not appropriate for this forum, but would be nice to know about alternatives if this is slow or something.