dumbmatter / fakeIndexedDB

A pure JS in-memory implementation of the IndexedDB API
Apache License 2.0
562 stars 69 forks source link

Problem storing and retrieving CryptoKeys #86

Closed oed closed 1 year ago

oed commented 1 year ago

I'm having some issues storing and retrieving CryptoKeys using fake-indexeddb in nodejs.

The following code works in browser (tested in firefox), without the imports:

import { webcrypto } from 'crypto'
const window = { crypto: webcrypto }
import indexedDB from 'fake-indexeddb'

// Open an IndexedDB transaction and get the object store
function getObjectStore(storeName, mode) {
    return new Promise((resolve, reject) => {
        var dbRequest = indexedDB.open("keyDatabase", 1);

        dbRequest.onupgradeneeded = function(event) {
            let db = event.target.result;
            db.createObjectStore(storeName);
        };

        dbRequest.onsuccess = function(event) {
            let db = event.target.result;
            let transaction = db.transaction(storeName, mode);
            let objectStore = transaction.objectStore(storeName);
            resolve(objectStore);
        };

        dbRequest.onerror = function(event) {
            reject('Error opening database.');
        };
    });
}

// Generate the crypto key
window.crypto.subtle.generateKey(
    {
        name: "ECDSA",
        namedCurve: "P-256"
    },
    false,  // not extractable
    ["sign", "verify"]
).then(function(key) {
    console.log('stored key', key)
    // Store the key in IndexedDB
    getObjectStore('keyStore', 'readwrite').then(function(store) {
        let request = store.add(key, 'myKey');
        request.onsuccess = function() {
            console.log('Key stored successfully');
        };
        request.onerror = function() {
            console.log('Error storing the key');
        };
    });
});

// Retrieve the key from IndexedDB
getObjectStore('keyStore', 'readonly').then(function(store) {
    let request = store.get('myKey');
    request.onsuccess = function() {
        let key = request.result;
        console.log('retrieved key', key)

        // Now sign the string
        let data = new TextEncoder().encode("test string 123");
        window.crypto.subtle.sign(
            {
                name: "ECDSA",
                hash: {name: "SHA-256"}
            },
            key.privateKey,
            data
        ).then(function(signature) {
            // Do something with the signature...
            // Note that ArrayBuffer signature is not directly readable
            // We can convert it to a hexadecimal string for display
            let signatureHex = Array.from(new Uint8Array(signature)).map(b => ('00' + b.toString(16)).slice(-2)).join('');
            console.log("Signature:", signatureHex);
        });
    };
});

However, when I run this code in nodejs I get this error:

$ node src/testidb.js
stored key {
  publicKey: CryptoKey {
    type: 'public',
    extractable: true,
    algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
    usages: [ 'verify' ]
  },
  privateKey: CryptoKey {
    type: 'private',
    extractable: false,
    algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
    usages: [ 'sign' ]
  }
}
Key stored successfully
retrieved key { publicKey: {}, privateKey: {} }
node:internal/errors:477
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "key" argument must be an instance of CryptoKey. Received an instance of Object
    at new NodeError (node:internal/errors:387:5)
    at signVerify (node:internal/crypto/webcrypto:645:11)
    at SubtleCrypto.sign (node:internal/crypto/webcrypto:684:10)
    at FDBRequest.request.onsuccess (file:///Users/joel/code/js-did/packages/did-session/src/testidb.js:60:30)
    at invokeEventListeners (file:///Users/joel/code/js-did/node_modules/.pnpm/fake-indexeddb@4.0.2/node_modules/fake-indexeddb/build/esm/lib/FakeEventTarget.js:48:25)
    at FDBRequest.dispatchEvent (file:///Users/joel/code/js-did/node_modules/.pnpm/fake-indexeddb@4.0.2/node_modules/fake-indexeddb/build/esm/lib/FakeEventTarget.js:92:7)
    at FDBTransaction._start (file:///Users/joel/code/js-did/node_modules/.pnpm/fake-indexeddb@4.0.2/node_modules/fake-indexeddb/build/esm/FDBTransaction.js:195:19)
    at Immediate.<anonymous> (file:///Users/joel/code/js-did/node_modules/.pnpm/fake-indexeddb@4.0.2/node_modules/fake-indexeddb/build/esm/lib/Database.js:30:16)
    at processImmediate (node:internal/timers:466:21) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Obviously the specific error here matters less. As you can see the stored key and retrieved key are very different.

Am I doing something wrong or is this a bug/limitation with fake-indexedb?

dumbmatter commented 1 year ago

Interesting, I ran your code and it seems to be working:

stored key {
  publicKey: CryptoKey {
    type: 'public',
    extractable: true,
    algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
    usages: [ 'verify' ]
  },
  privateKey: CryptoKey {
    type: 'private',
    extractable: false,
    algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
    usages: [ 'sign' ]
  }
}
Key stored successfully
retrieved key {
  publicKey: CryptoKey {
    type: 'public',
    extractable: true,
    algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
    usages: [ 'verify' ]
  },
  privateKey: CryptoKey {
    type: 'private',
    extractable: false,
    algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
    usages: [ 'sign' ]
  }
}
Signature: 65b18018284eb4ead0b066f524fd89c636e4f2d50254700c052c6c6551f57b98c168c0cda50908790304dea0eb061a7bd302a722c4ed366c5f1c7a0e41373d75

I'm using Node.js version 18.17.0, what version are you using? And any other details of your environment, if that doesn't solve it?

oed commented 1 year ago

I was using Node.js v16.17.0.

Changed to v18.17.1 and it seems to work. Thanks!