Code Monkey home page Code Monkey logo

keystore-idb's Introduction

IndexedDB KeyStore

NPM License Maintainability Built by FISSION Discord Discourse

In-browser key management with IndexedDB and the Web Crypto API.

Securely store and use keys for encryption, decryption, and signatures. IndexedDB and Web Crypto keep keys safe from malicious javascript.

Supports both RSA (RSASSA-PKCS1-v1_5 & RSA-OAEP) and Elliptic Curves (P-256, P-381 & P-521).

ECC (Elliptic Curve Cryptography) is only available on Chrome. Firefox and Safari do not support ECC and must use RSA. Specifically, this is an issue with storing ECC keys in IndexedDB

Config

Below is the default config and all possible values Note: these are given as primitives, but in Typescript you can use the included enums

const defaultConfig = {
  type: 'ecc', // 'ecc' | 'rsa'
  curve: 'P-256', // 'P-256' | 'P-384' | 'P-521'
  rsaSize: 2048, // 1024 | 2048 | 4096
  symmAlg: 'AES-CTR', // 'AES-CTR' | 'AES-GCM' | 'AES-CBC'
  symmLen: 128, // 128 | 192 | 256
  hashAlg: 'SHA-256', // 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'
  charSize: 16, // 8 | 16
  storeName: 'keystore', // any string
  exchangeKeyName: 'exchange-key', // any string
  writeKeyName: 'write-key', // any string
}

Note: if you don't include a crypto "type" ('ecc' | 'rsa'), the library will check if your browser supports ECC. If so (Chrome), it will use ECC, if not (Firefox, Safari) it will fall back to RSA.

Example Usage

import keystore from 'keystore-idb'

async function run() {
  await keystore.clear()

  const ks1 = await keystore.init({ storeName: 'keystore' })
  const ks2 = await keystore.init({ storeName: 'keystore2' })

  const msg = "Incididunt id ullamco et do."

  // exchange keys and write keys are separate because of the Web Crypto API
  const exchangeKey1 = await ks1.publicExchangeKey()
  const writeKey1 = await ks1.publicWriteKey()
  const exchangeKey2 = await ks2.publicExchangeKey()

  // these keys get exported as strings
  console.log('exchangeKey1: ', exchangeKey1)
  console.log('writeKey1: ', writeKey1)
  console.log('exchangeKey2: ', exchangeKey2)

  const sig = await ks1.sign(msg)
  const valid = await ks2.verify(msg, sig, writeKey1)
  console.log('sig: ', sig)
  console.log('valid: ', valid)

  const cipher = await ks1.encrypt(msg, exchangeKey2)
  const decipher = await ks2.decrypt(cipher, exchangeKey1)
  console.log('cipher: ', cipher)
  console.log('decipher: ', decipher)
}

run()

Development

# install dependencies
yarn

# run development server
yarn start

# build
yarn build

# test
yarn test

# test w/ reloading
yarn test:watch

# publish (run this script instead of npm publish!)
./publish.sh

keystore-idb's People

Contributors

appcypher avatar dependabot[bot] avatar dholms avatar icidasset avatar matheus23 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

keystore-idb's Issues

Use utf8 for string <-> byte array conversions

At the moment, one has to specify the "char size" when using operations that may convert between strings and bytes.
This char size can be either 8 or 16 (bits).
If it's 8 bits, the conversion will only work for ascii strings, if it's 16 it'll encode everything using more bits per character, but can still be error-prone.

For example:
image

At least that's what I think is going on? It's hard to really understand what's happening here. In practice this hasn't bitten us, because we mostly use base64 for any bytes that could be anything, and that fits into ASCII again, but I think this is quite error prone in general, and we should instead just use uint8arrays.fromString and uint8arrays.toString, which defaults to utf8.

Running 'yarn build' fails with TypeScript error

Summary

Running yarn build fails.

MacOS: 10.15.5
Node: 12.18.1
Typescript: 2.7.9

WIth the output:

src/ecc/keys.ts:27:10 - error TS2769: No overload matches this call.
  Overload 1 of 3, '(format: "jwk", keyData: JsonWebKey, algorithm: string | Algorithm | AesKeyAlgorithm | EcKeyImportParams | HmacImportParams | RsaHashedImportParams | DhImportKeyParams, extractable: boolean, keyUsages: KeyUsage[]): PromiseLike<...>', gave the following error.
    Argument of type '"raw"' is not assignable to parameter of type '"jwk"'.
  Overload 2 of 3, '(format: "raw" | "pkcs8" | "spki", keyData: ArrayBuffer | DataView | Int8Array | Uint8Array | Uint8ClampedArray | ... 5 more ... | Float64Array, algorithm: string | ... 5 more ... | DhImportKeyParams, extractable: boolean, keyUsages: KeyUsage[]): PromiseLike<...>', gave the following error.
    Argument of type 'string[]' is not assignable to parameter of type 'KeyUsage[]'.
  Overload 3 of 3, '(format: string, keyData: ArrayBuffer | DataView | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | ... 5 more ... | JsonWebKey, algorithm: string | ... 5 more ... | DhImportKeyParams, extractable: boolean, keyUsages: KeyUsage[]): PromiseLike<...>', gave the following error.
    Argument of type 'string[]' is not assignable to parameter of type 'KeyUsage[]'.

 27   return window.crypto.subtle.importKey(
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 28     'raw',
    ~~~~~~~~~~
... 
 32     uses
    ~~~~~~~~
 33   )
    ~~~

Setup build tools

Problem

Build & code quality tools aren't set up

Solution

Setup Travis & Code Climate. Fix issues so they're passing and have an A rating

Streaming encryption/decryption

Problem

WebCrypto does not support streaming encryption/decryption. This means that if we want to display a large file in the browser, we need to read the entire file into memory, decrypt it, and the stream it to the renderer.
w3c/webcrypto#73

Solution

We're using AES-CTR, so after calculating the nonce, the counter is predictable for each block.

W'll need to read one block at a time, encrypt, write to outStream & increment the counter

RSA-PKCS

Problem

RSA-PKCS is the more prevalent RSA signature scheme and is not supported

Solution

Support & default to RSA-PKCS. Offer RSA-PSS as an alternative

Byte operations on keystore interface

Problem

Keystore interface only exposes string operations. To use byte operations you need to do a lot of stuff by hand.

Solution

Add byte operations to keystore interface

Firefox cannot store EC private keys in IndexedDB

Problem

Google Chrome is able to store any key (that I've tried at least) in IndexedDB with no issues.

Firefox is able to store symmetric AES keys as well as RSA private keys, but throws the following error when attempting to store an ECDH/ECDSA private key:
DataCloneError: The object could not be cloned.
Note: it is specifically the private key that causes this, not the public

This is strange because the Mozilla recommendation for key management with the Web Crypto API is to store them in IndexedDB

This has to do with the way that Firefox has implemented the Structured Clone Algorithm which is an HTML specification used for storage in IndexedDB but also in other cases of data transfer such as communication between Workers via postMessage (you can see my tests of structuralClone using postMessage in src/utils.ts).

The only relevant bug report I could find is Here. Although I've tried this with both an exportable and a non-exportable key and it doesn't work either way. Apparently that issue is reliant on this one which hasn't shown any movement in 2yrs.

Others have run into this issue as well: localForage/localForage#844 (comment) And it seems that the issue is also present on Safari (I'll rely on someone else to give that a shot ๐Ÿ˜‰)

Solution

Unclear right now. Needs more investigation/discussion.

What's the path forward if we can't get ECDSA/ECDH keys in Firefox/Safari? RSA keys? Or use something other than WebCrypto/IndexedDB?

Add self encrypt/decrypt methods

Problem

Sometimes you want to encrypt/decrypt something for yourself but with the familiar asymm interface

Solution

Add selfEncrypt & selfDecrypt functions to KeyStore interface

document iv usage for aes

Summary

I am trying to use the aes encrypt and decrypt functions and i can't quite grok whats up with the iv param

Problem

when i derive my shared key using ECDH with my own private and public keys i can successfully decrypt, but if i derive a shared key with another users public key, the decryption fails.

I narrowed it down to 2 possiblities

  1. i am doing something stupid (not "subtle" enough) with derivation and or padding or non padding
  2. something is fishy with the way keystore is dealing with iv

so, is it "customary" to tack the iv to the beginning of the encrypted payload?
...seems a bit shady, or?

so, i am sure that this is a request for documentation and strategy explanation, and i am not sure if may also be a bug.

Thanks!

Make webcrypto API calls node/browser agnostic

At the moment, we're accessing the webcrypto api via globalThis.crypto.subtle. To do the same in nodejs, we'd need to access globalThis.crypto.webcrypto.subtle.
We should try to figure out which one to use dynamically. Something like this:

export const crypto: Crypto = (globalThis.crypto as any).webcrypto || globalThis.crypto

if (crypto == null || crypto.subtle == null) {
  throw new Error("Couldn't find access to the WebCrypto API on this platform.")
}

export const webcrypto: SubtleCrypto = crypto.subtle

(here crypto can be used for crypto.getRandomValues() and webcrypto is the same as sublte above).


This helps making the project work in nodejs eventually.

Add exports map to package.json

It's common to import typescript sources using e.g. import "keystore-idb/utils". This will work in jest with typescript.

This will also work when code is bundled with e.g. esbuild which has similar import semantics to typescript.

Internal Imports

However, if we run tsc, the internal imports won't work, because typescript doesn't transform imports (it keeps them as-is), and import "keystore-idb/utils" is not a valid import for nodejs, because the file is called utils.js, not utils.

The solution (used in webnative) is to use a transformation plugin in typescript, which inserts .js imports.

External Imports

Finally, to make external imports work, we can use an exports map. Then, imports like import "keystore-idb/utils" will work both in nodejs, browser, webpack, vite and in jest.

Question: Explain Example

Can someone help me understand the example? It's not clear to me how you're able to decrypt using a key pair that is different from what was used to encrypt it. I've ran the example and can see that each key is actually a key pair with a public and private key. I would expect the encrypt and decrypt to use the same key pair, where the encrypt uses the public key and the decrypt uses the private key. This would seem more aligned with the description of asymmetric encryption.

const cipher = await ks1.encrypt(msg, exchangeKey2)
const decipher = await ks2.decrypt(cipher, exchangeKey1)

I'm sure I'm missing something, but can't pinpoint what it is yet.

Integration Tests

Problem

Lots of unit tests but no integration tests.

Solution

Add integration test with something like Selenium

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.