New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@lo-fi/local-data-lock

Package Overview
Dependencies
Maintainers
0
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lo-fi/local-data-lock - npm Package Compare versions

Comparing version 0.9.5 to 0.10.0

4

dist/auto/ldl.js
/*! Local-Data-Lock: ldl.js
v0.9.5 (c) 2024 Kyle Simpson
v0.10.0 (c) 2024 Kyle Simpson
MIT License: http://getify.mit-license.org
*/
import{supportsWebAuthn as e,regDefaults as t,register as r,authDefaults as n,auth as a,verifyAuthResponse as i,packPublicKeyJSON as o,unpackPublicKeyJSON as s,toBase64String as c,fromBase64String as l,toUTF8String as y,fromUTF8String as u,resetAbortReason as p}from"@lo-fi/webauthn-local-client";const d=sodium.crypto_sign_SEEDBYTES;var f=setMaxLockKeyCacheLifetime(),k=function loadLocalIdentities(){return Object.fromEntries(Object.entries(JSON.parse(window.localStorage.getItem("local-identities")||null)||{}).filter((([e,t])=>"number"==typeof t.lastSeq&&Array.isArray(t.passkeys)&&t.passkeys.length>0&&t.passkeys.every((e=>"string"==typeof e.credentialID&&""!=e.credentialID&&"number"==typeof e.seq&&null!=e.publicKey&&"object"==typeof e.publicKey&&"number"==typeof e.publicKey.algoCOSE&&"string"==typeof e.publicKey.raw&&""!=e.publicKey.raw&&"string"==typeof e.publicKey.spki&&""!=e.publicKey.spki&&"string"==typeof e.hash&&""!=e.hash&&e.hash==computePasskeyEntryHash(e))))).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:s(e.publicKey)})))}])))}(),b={},m=null;export{e as supportsWebAuthn,o as packPublicKeyJSON,s as unpackPublicKeyJSON,c as toBase64String,l as fromBase64String,y as toUTF8String,u as fromUTF8String,listLocalIdentities,clearLockKeyCache,removeLocalAccount,getLockKey,generateEntropy,deriveLockKey,lockData,unlockData,setMaxLockKeyCacheLifetime};var K={supportsWebAuthn:e,packPublicKeyJSON:o,unpackPublicKeyJSON:s,toBase64String:c,fromBase64String:l,toUTF8String:y,fromUTF8String:u,listLocalIdentities:listLocalIdentities,clearLockKeyCache:clearLockKeyCache,removeLocalAccount:removeLocalAccount,getLockKey:getLockKey,generateEntropy:generateEntropy,deriveLockKey:deriveLockKey,lockData:lockData,unlockData:unlockData,setMaxLockKeyCacheLifetime:setMaxLockKeyCacheLifetime};export default K;function listLocalIdentities(){return Object.keys(k)}function cacheLockKey(e,t,r=!1){e in b&&!r||(b[e]={...t,timestamp:Date.now()})}function clearLockKeyCache(e){null!=e?delete b[e]:b={}}function removeLocalAccount(e){delete b[e],delete k[e],storeLocalIdentities()}async function getLockKey({localIdentity:e=c(generateEntropy(15)),username:o="local-user",displayName:s="Local User",relyingPartyID:l=document.location.hostname,relyingPartyName:y="Local Data Lock",addNewPasskey:u=!1,resetLockKey:p=!1,verify:K=!0}={}){var g=null!=e?k[e]:null;if(null!=g){let t=function getCachedLockKey(e){if(e in b&&b[e].timestamp>=Date.now()-f){let{timestamp:t,...r}=b[e];return r}}(e);if(null==t||p){if(delete b[e],p)return resetAbortToken(),({record:k[e],lockKey:t}=await registerLocalIdentity()),storeLocalIdentities(),cacheLockKey(e,t),{...t,localIdentity:e};if(u)throw new Error("Encryption/Decryption key not currently cached, unavailable for new passkey");{resetAbortToken();let t=n({relyingPartyID:l,mediation:"optional",allowCredentials:g.passkeys.map((({credentialID:e})=>({type:"public-key",id:e}))),signal:m.signal}),r=await a(t);if(K){let e=g.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}return{...extractLockKey(r),localIdentity:e}}}if(u){resetAbortToken();let{record:e}=await registerLocalIdentity(t);g.lastSeq=e.lastSeq,g.passkeys=[...g.passkeys,...e.passkeys],storeLocalIdentities()}return{...t,localIdentity:e}}if(u){resetAbortToken();let{record:t,lockKey:r}=await registerLocalIdentity();return k[e]=t,cacheLockKey(e,r),storeLocalIdentities(),{...r,localIdentity:e}}{resetAbortToken();let t=n({relyingPartyID:l,mediation:"optional",signal:m.signal}),r=await a(t),o=extractLockKey(r),[s]=Object.entries(k).find((([,e])=>null!=e.passkeys.find((e=>e.credentialID==r.response.credentialID))))||[];if(null!=s){if(delete b[e],g=k[e=s],K){let e=g.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}cacheLockKey(e,o)}else if(K)throw new Error("Auth verification requested but skipped, against unrecognized passkey (no matching local-identity)");return{...o,localIdentity:e}}async function registerLocalIdentity(n=deriveLockKey()){try{let i=((k[e]||{}).lastSeq||0)+1,c=new Uint8Array(n.iv.byteLength+2),u=new DataView(new ArrayBuffer(2));u.setInt16(0,i,!1),c.set(n.iv,0),c.set(new Uint8Array(u.buffer),n.iv.byteLength);let p=t({relyingPartyID:l,relyingPartyName:y,user:{id:c,name:o,displayName:s},signal:m.signal}),d=await r(p);return{record:{lastSeq:i,passkeys:[(a={seq:i,credentialID:d.response.credentialID,publicKey:d.response.publicKey},{...a,hash:computePasskeyEntryHash(a)})]},lockKey:n}}catch(e){throw console.log(e),new Error("Identity/Passkey registration failed",{cause:e})}var a}function extractLockKey(t){try{if(t&&t.response&&isByteArray(t.response.userID)&&t.response.userID.byteLength==d+2){let r=deriveLockKey(t.response.userID.subarray(0,d));return cacheLockKey(e,r),r}throw new Error("Passkey info missing")}catch(e){throw new Error("Chosen passkey did not provide a valid encryption/decryption key",{cause:e})}}}function resetAbortToken(){m&&m.abort("Passkey operation abandoned."),m=new AbortController}function generateEntropy(e=16){return sodium.randombytes_buf(e)}function deriveLockKey(e=generateEntropy(d)){try{let t=sodium.crypto_sign_seed_keypair(e);return{iv:e,publicKey:t.publicKey,privateKey:t.privateKey,encPK:sodium.crypto_sign_ed25519_pk_to_curve25519(t.publicKey),encSK:sodium.crypto_sign_ed25519_sk_to_curve25519(t.privateKey)}}catch(e){throw new Error("Encryption/decryption key derivation failed.",{cause:e})}}function lockData(e,t,{outputFormat:r="base64"}={}){try{let n=null==e?null:e instanceof ArrayBuffer?new Uint8Array(e):isByteArray(e)?e:u("object"==typeof e?JSON.stringify(e):"string"==typeof e?e:String(e));if(null==e)throw new Error("Non-empty data required.");let a=sodium.crypto_box_seal(n,t.encPK);return["base64","base-64"].includes(r.toLowerCase())?c(a):a}catch(e){throw new Error("Data encryption failed.",{cause:e})}}function unlockData(e,t,{outputFormat:r="utf8",parseJSON:n=!0}={}){try{let a=sodium.crypto_box_seal_open("string"==typeof e?l(e):e,t.encPK,t.encSK);if(["utf8","utf-8"].includes(r.toLowerCase())){let e=y(a);return n?JSON.parse(e):e}return a}catch(e){throw new Error("Data decryption failed.",{cause:e})}}function storeLocalIdentities(){var e=Object.fromEntries(Object.entries(k).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:o(e.publicKey)})))}])));Object.keys(e).length>0?window.localStorage.setItem("local-identities",JSON.stringify(e)):window.localStorage.removeItem("local-identities")}function setMaxLockKeyCacheLifetime(e=18e5){return f=Math.max(0,Number(e)||0)}function isByteArray(e){return e instanceof Uint8Array&&e.buffer instanceof ArrayBuffer}function computePasskeyEntryHash(e){let{hash:t,...r}=e;return c(sodium.crypto_hash(JSON.stringify({...r,publicKey:o(r.publicKey)})))}
import{supportsWebAuthn as e,regDefaults as t,register as r,authDefaults as n,auth as o,verifyAuthResponse as i,packPublicKeyJSON as a,unpackPublicKeyJSON as c,toBase64String as s,fromBase64String as l,toUTF8String as y,fromUTF8String as u,resetAbortReason as p}from"@lo-fi/webauthn-local-client";const d=1,f=sodium.crypto_sign_SEEDBYTES;var k=setMaxLockKeyCacheLifetime(),K=function loadLocalIdentities(){return Object.fromEntries(Object.entries(JSON.parse(window.localStorage.getItem("local-identities")||null)||{}).filter((([e,t])=>"number"==typeof t.lastSeq&&Array.isArray(t.passkeys)&&t.passkeys.length>0&&t.passkeys.every((e=>"string"==typeof e.credentialID&&""!=e.credentialID&&"number"==typeof e.seq&&null!=e.publicKey&&"object"==typeof e.publicKey&&"number"==typeof e.publicKey.algoCOSE&&"string"==typeof e.publicKey.raw&&""!=e.publicKey.raw&&"string"==typeof e.publicKey.spki&&""!=e.publicKey.spki&&"string"==typeof e.hash&&""!=e.hash&&e.hash==computePasskeyEntryHash(e))))).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:c(e.publicKey)})))}])))}(),b={},h=null;export{e as supportsWebAuthn,a as packPublicKeyJSON,c as unpackPublicKeyJSON,s as toBase64String,l as fromBase64String,y as toUTF8String,u as fromUTF8String,listLocalIdentities,clearLockKeyCache,removeLocalAccount,getLockKey,generateEntropy,deriveLockKey,lockData,unlockData,setMaxLockKeyCacheLifetime};var L={supportsWebAuthn:e,packPublicKeyJSON:a,unpackPublicKeyJSON:c,toBase64String:s,fromBase64String:l,toUTF8String:y,fromUTF8String:u,listLocalIdentities:listLocalIdentities,clearLockKeyCache:clearLockKeyCache,removeLocalAccount:removeLocalAccount,getLockKey:getLockKey,generateEntropy:generateEntropy,deriveLockKey:deriveLockKey,lockData:lockData,unlockData:unlockData,setMaxLockKeyCacheLifetime:setMaxLockKeyCacheLifetime};export default L;function listLocalIdentities(){return Object.keys(K)}function cacheLockKey(e,t,r=!1){e in b&&!r||(b[e]={...t,timestamp:Date.now()})}function clearLockKeyCache(e){null!=e?delete b[e]:b={}}function removeLocalAccount(e){delete b[e],delete K[e],storeLocalIdentities()}async function getLockKey({localIdentity:e=s(generateEntropy(15)),username:a="local-user",displayName:c="Local User",relyingPartyID:l=document.location.hostname,relyingPartyName:y="Local Data Lock",addNewPasskey:u=!1,resetLockKey:p=!1,useLockKey:d=null,verify:L=!0}={}){var m=null!=e?K[e]:null;if(null!=m){let t=function getCachedLockKey(e){if(e in b&&b[e].timestamp>=Date.now()-k){let{timestamp:t,...r}=b[e];return r}}(e);if(null==t||p){if(delete b[e],p)return resetAbortToken(),({record:K[e],lockKey:t}=await registerLocalIdentity(d&&"object"==typeof d?checkLockKey(d):void 0)),storeLocalIdentities(),cacheLockKey(e,t),{...t,localIdentity:e};if(u)throw new Error("Encryption/Decryption key not currently cached, unavailable for new passkey");{resetAbortToken();let t=n({relyingPartyID:l,mediation:"optional",allowCredentials:m.passkeys.map((({credentialID:e})=>({type:"public-key",id:e}))),signal:h.signal}),r=await o(t);if(L){let e=m.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}return{...extractLockKey(r),localIdentity:e}}}if(u){resetAbortToken();let{record:e}=await registerLocalIdentity(t);m.lastSeq=e.lastSeq,m.passkeys=[...m.passkeys,...e.passkeys],storeLocalIdentities()}return{...t,localIdentity:e}}if(u){resetAbortToken();let{record:t,lockKey:r}=await registerLocalIdentity(d&&"object"==typeof d?checkLockKey(d):void 0);return K[e]=t,cacheLockKey(e,r),storeLocalIdentities(),{...r,localIdentity:e}}{resetAbortToken();let t=n({relyingPartyID:l,mediation:"optional",signal:h.signal}),r=await o(t),a=extractLockKey(r),[c]=Object.entries(K).find((([,e])=>null!=e.passkeys.find((e=>e.credentialID==r.response.credentialID))))||[];if(null!=c){if(delete b[e],m=K[e=c],L){let e=m.passkeys.find((e=>e.credentialID==r.response.credentialID)),t=null!=e?e.publicKey:null;if(!(null!=t&&await i(r.response,t)))throw new Error("Auth verification failed")}cacheLockKey(e,a)}else if(L)throw new Error("Auth verification requested but skipped, against unrecognized passkey (no matching local-identity)");return{...a,localIdentity:e}}async function registerLocalIdentity(n=deriveLockKey()){try{let i=((K[e]||{}).lastSeq||0)+1,s=new Uint8Array(n.iv.byteLength+2),u=new DataView(new ArrayBuffer(2));u.setInt16(0,i,!1),s.set(n.iv,0),s.set(new Uint8Array(u.buffer),n.iv.byteLength);let p=t({relyingPartyID:l,relyingPartyName:y,user:{id:s,name:a,displayName:c},signal:h.signal}),d=await r(p);return{record:{lastSeq:i,passkeys:[(o={seq:i,credentialID:d.response.credentialID,publicKey:d.response.publicKey},{...o,hash:computePasskeyEntryHash(o)})]},lockKey:n}}catch(e){throw new Error("Identity/Passkey registration failed",{cause:e})}var o}function extractLockKey(t){try{if(t&&t.response&&isByteArray(t.response.userID)&&t.response.userID.byteLength==f+2){let r=deriveLockKey(t.response.userID.subarray(0,f));return cacheLockKey(e,r),r}throw new Error("Passkey info missing")}catch(e){throw new Error("Chosen passkey did not provide a valid encryption/decryption key",{cause:e})}}}function resetAbortToken(){h&&h.abort("Passkey operation abandoned."),h=new AbortController}function generateEntropy(e=16){return sodium.randombytes_buf(e)}function deriveLockKey(e=generateEntropy(f)){try{let t=sodium.crypto_sign_seed_keypair(e);return{keyFormatVersion:d,iv:e,publicKey:t.publicKey,privateKey:t.privateKey,encPK:sodium.crypto_sign_ed25519_pk_to_curve25519(t.publicKey),encSK:sodium.crypto_sign_ed25519_sk_to_curve25519(t.privateKey)}}catch(e){throw new Error("Encryption/decryption key derivation failed.",{cause:e})}}function checkLockKey(e){if(e&&"object"==typeof e){if(e.keyFormatVersion===d)return e;if(isByteArray(e.iv)&&e.iv.byteLength==f)return deriveLockKey(e.iv)}throw new Error("Unrecongnized lock-key")}function lockData(e,t,{outputFormat:r="base64"}={}){try{let n=null==e?null:e instanceof ArrayBuffer?new Uint8Array(e):isByteArray(e)?e:u("object"==typeof e?JSON.stringify(e):"string"==typeof e?e:String(e));if(null==e)throw new Error("Non-empty data required.");let o=sodium.crypto_box_seal(n,t.encPK);return["base64","base-64"].includes(r.toLowerCase())?s(o):o}catch(e){throw new Error("Data encryption failed.",{cause:e})}}function unlockData(e,t,{outputFormat:r="utf8",parseJSON:n=!0}={}){try{let o=sodium.crypto_box_seal_open("string"==typeof e?l(e):e,t.encPK,t.encSK);if(["utf8","utf-8"].includes(r.toLowerCase())){let e=y(o);return n?JSON.parse(e):e}return o}catch(e){throw new Error("Data decryption failed.",{cause:e})}}function storeLocalIdentities(){var e=Object.fromEntries(Object.entries(K).map((([e,t])=>[e,{...t,passkeys:t.passkeys.map((e=>({...e,publicKey:a(e.publicKey)})))}])));Object.keys(e).length>0?window.localStorage.setItem("local-identities",JSON.stringify(e)):window.localStorage.removeItem("local-identities")}function setMaxLockKeyCacheLifetime(e=18e5){return k=Math.max(0,Number(e)||0)}function isByteArray(e){return e instanceof Uint8Array&&e.buffer instanceof ArrayBuffer}function computePasskeyEntryHash(e){let{hash:t,...r}=e;return s(sodium.crypto_hash(JSON.stringify({...r,publicKey:a(r.publicKey)})))}
{
"name": "@lo-fi/local-data-lock",
"description": "Protect local-first app data with encryption/decryption key secured in Webauthn (biometric) passkeys",
"version": "0.9.5",
"version": "0.10.0",
"exports": {

@@ -6,0 +6,0 @@ ".": "./dist/bundlers/ldl.mjs",

@@ -6,4 +6,14 @@ # Local Data Lock

**Local Data Lock** provides a simple utility interface for encrypting and decrypting local-first application data using a keypair stored and protected by [Webauthn](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) (biometric passkeys).
**Local Data Lock** provides a simple utility interface for encrypting and decrypting local-first application data using a keypair stored and protected by [Webauthn](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) (biometric passkeys), via the [**Webauthn Local Client** library](https://github.com/mylofi/webauthn-local-client) -- no servers required!
```js
var lockKey = await getLockKey({ .. });
var encData = await lockData({ hello: "World!" },lockKey);
// "aG4/z..."
await unlockData(encData,lockKey);
// { hello: "World!" }
```
----

@@ -15,16 +25,28 @@

The intent of this library is to store encrypted data on the device, and protect the encryption/decryption keypair securely in a passkey that the user can access by presenting their biometric factor(s). Further, the cryptographic keypair may also be used for secured asymmetric data transmission.
## Overview
The primary dependency of this library is [**WebAuthn-Local-Client**](https://github.com/mylofi/webauthn-local-client), which wraps the [WebAuthn API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) for managing passkeys entirely in the local client (zero servers).
This library can securely lock (encrypt) data in the local client, with no servers needed. The encrypted data *might also be* stored locally on the client device; for this purpose, please strongly consider using the [**Local Vault** library](https://github.com/mylofi/local-vault).
**Local Data Lock** generates an encryption/decryption keypair, storing that securely in the passkey (via its `userHandle` field), which is protected by the authenticator/device. The library also stores entries for these passkeys in the device's [`LocalStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) -- specifically, the public-key info for the passkey itself, which is necessary for **verifying** subsequent passkey authentication responses.
However, the encrypted data (by default, represented as a base64 encoded string) might be transmitted and stored elsewhere, such as on an app's servers. The cryptographic keypair may be used for digital signatures to verify secure data transmission.
**NOTE:** This public-key for a passkey is *NOT* in any way related to the encryption/decryption keypair, which **Local Data Lock** does not persist anywhere on the device (only kept in memory). It's *only* used for authentication verification (protecting against MitM attacks on the device biometric system). Verification defaults to on, but can be skipped by passing `verify: false` as an option to the `getLockKey()` method.
This cryptographic keypair is protected locally on the user's device in a biometric passkey; the user can easily unlock (decrypt) their data, or verify a received data transmission from their other device, by presenting a biometric factor to retrieve the keypair.
Your application accesses the encryption/decryption keypair via `getLockKey()`, and may optionally decide if you want to persist it somewhere -- for more convenience/ease-of-use, as compared to asking the user to re-authenticate their passkey on each usage. But you are cautioned to be very careful in such decisions, striking an appropriate balance between security and convenience.
### How does it work?
To assist in making these difficult tradeoffs, **Local Data Lock** internally caches the encryption/decryption key after a successful passkey authentication, and keeps it in memory (assuming no page refresh) for a period of time (by default, 30 minutes); a user won't need to re-authenticate their passkey more often than once per 30 minutes. This default time threshold can also be adjusted from 0ms or higher, using the `setMaxLockKeyCacheLifetime()` method.
The direct dependency of this library is [**WebAuthn-Local-Client**](https://github.com/mylofi/webauthn-local-client), which utilizes the browser's [WebAuthn API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) for managing biometric passkeys entirely in the local client (zero servers).
You are strongly encouraged **NOT** to persist the encryption/decryption key, and to utilize this time-based caching mechanism.
The cryptographic keypair the library generates, is attached securely to a passkey (via its `userHandle` field), which is protected by the authenticator/device. The library also stores meta-data entries for these passkeys in the device's [`LocalStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) -- specifically, the public-key info for the passkey itself, which is necessary for **verifying** subsequent passkey authentication responses.
**NOTE:** This public-key for a passkey is *NOT* in any way related to the crytographic keypair, which **Local Data Lock** does not persist anywhere on the device (only kept in memory). It's *only* used for authentication verification -- protecting against MitM attacks against the authenticator. Verification defaults *on*, but can be skipped by passing `verify: false` as an option to the `getLockKey()` method.
### Security vs Convenience
Your application accesses the cryptographic keypair via `getLockKey()`, and may optionally decide if you want to persist it somewhere -- for more convenience/ease-of-use, as compared to asking the user to re-authenticate their passkey on each usage. But you are cautioned to be very careful in such decisions, striking an appropriate balance between security and convenience.
If the design is *too convenient* (e.g., once-forever logins), it's likely to be insecure (and the user may not realize it!). If the design is *too secure*, it's likely to have so much UX friction that users won't use it (or your app).
To assist in making these difficult tradeoffs, **Local Data Lock** internally caches the cryptographic keypair after a successful passkey authentication, and keeps it in memory (assuming no page refresh) for a period of time (by default, 30 minutes); in such a setup, the user won't need to re-authenticate their passkey more often than once per 30 minutes. This default time threshold can also be adjusted, from 0ms upward, using the `setMaxLockKeyCacheLifetime()` method.
You are strongly encouraged **NOT** to persist the encryption/decryption key, and to instead rely on this time-based caching mechanism.
## Deployment / Import

@@ -60,3 +82,3 @@

## Registering a local account
## Registering a local account (and lock-key keypair)

@@ -79,2 +101,45 @@ A "local account" is merely a collection of one or more passkeys that are all holding the same encryption/decryption keypair. There's no limit on the number of "local account" passkey collections on a device (other than device storage limits).

### Lock-Key Value Format
Other than reading the `localIdentity` property, the lock-key object should be **treated opaquely**, meaning that you don't rely on its structure, don't make any changes to it, etc.
It contains binary data for the keypairs, in the form of various `Uint8Array` values. These types of data are not, as-is, particularly serialization safe (JSON, etc), for the purposes of storage or transmission. To serialize these binary-array values (and unserialize them later), you can use the `toBase64String()` / `fromBase64String()` utilities exported on the library's API.
For example, to serialize a lock-key for JSON-safe storage, or transmission:
```js
var serializedKey = Object.fromEntries(
Object.entries(key)
.map(([ prop, value ]) => [
prop,
(
value instanceof Uint8Array &&
value.buffer instanceof ArrayBuffer
) ?
toBase64String(value) :
value
])
);
```
And to deserialize:
```js
var key = Object.fromEntries(
Object.entries(serializedKey)
.map(([ prop, value ]) => [
prop,
(
typeof value == "string" &&
// padded base64 encoding of Uint8Array(32)
// will be at least 44 characters long
value.length >= 44
) ?
fromBase64String(value) :
value
])
);
```
### Obtaining the keypair from existing account/passkey

@@ -231,3 +296,3 @@

This keypair is suitable to use with `lockData()` and `unlockData()` methods. However, the keypair returned WILL NOT be associated with (or protected by) a device passkey; it receives no entry in the device's local-storage and will never be returned from `getLockKey()`. The intent of this library is to rely on passkeys, so you are encouraged *not* to pursue this manual approach unless strictly necessary.
This keypair is suitable to use with `lockData()` and `unlockData()` methods. However, the keypair returned WILL NOT be associated with (or protected by) a device passkey; it receives no entry in the device's local-storage and will not be returned from `getLockKey()`. The intent of this library is to rely on passkeys, so you are encouraged *not* to pursue this manual approach unless strictly necessary.

@@ -246,2 +311,29 @@ Further, to generate a suitable cryptograhpically random `seedValue`:

## Importing an encryption/decryption key
If you have a lock-key keypair generated by **Local Vault** / **Local Data Lock**, either from manually calling [`deriveLockKey()`](#deriving-an-encryptiondecryption-key), or from a previous call to `getLockKey()` (even on another device!), you *can* choose to import it to a local account.
When registering a new local-account:
```js
var key = await getLockKey({
addNewPasskey: true,
useLockKey: existingLockKey,
});
key === existingLockKey; // true
```
When resetting the key on an existing local-account:
```js
var key = await getLockKey({
localIdentitity: currentAccountID,
resetLockKey: true,
useLockKey: existingLockKey,
});
key === existingLockKey; // true
```
**Warning:** You should generally let **Local Data Lock** internally generate and manage the lock-keys on local-accounts, and should not store (or transmit) these lock-keys in a way that degrades the security promises of this library. Be very careful if you are using the library in a way that you need to use `useLockKey`, and make sure it's absolutely necessary.
## WebAuthn-Local-Client Utilities

@@ -278,13 +370,12 @@

To locally run the tests, start the simple static server (no server-side logic):
To instead run the tests locally, first make sure you've [already run the build](#re-building-dist), then:
```cmd
# only needed one time
npm install
npm run test:start
npm test
```
Then visit `http://localhost:8080/` in a browser.
This will start a static file webserver (no server logic), serving the interactive test page from `http://localhost:8080/`; visit this page in your browser to perform tests.
By default, the `test/test.js` file imports the code from the `src/*` directly. However, to test against the `dist/auto/*` files (as included in the npm package), you can modify `test/test.js`, updating the `/src` in its `import` statement to `/dist` (see the import-map in `test/index.html` for more details).
## License

@@ -291,0 +382,0 @@

@@ -20,2 +20,3 @@ import {

const CURRENT_LOCK_KEY_FORMAT_VERSION = 1;
const IV_BYTE_LENGTH = sodium.crypto_sign_SEEDBYTES;

@@ -127,5 +128,5 @@ var MAX_LOCK_KEY_CACHE_LIFETIME = setMaxLockKeyCacheLifetime();

relyingPartyName = "Local Data Lock",
addNewPasskey = false,
resetLockKey = false,
useLockKey = null,
verify = true,

@@ -162,3 +163,3 @@ } = {},

// create new lock-key (and passkey)?
// create (or import) new lock-key (and passkey)?
if (resetLockKey) {

@@ -173,4 +174,10 @@ resetAbortToken();

lockKey,
} = await registerLocalIdentity());
} = await registerLocalIdentity(
// manually importing an external lock-key?
useLockKey && typeof useLockKey == "object" ?
checkLockKey(useLockKey) :
undefined
));
storeLocalIdentities();
cacheLockKey(localID,lockKey);

@@ -280,3 +287,8 @@

resetAbortToken();
let { record, lockKey, } = await registerLocalIdentity();
let { record, lockKey, } = await registerLocalIdentity(
// manually importing an external lock-key?
useLockKey && typeof useLockKey == "object" ?
checkLockKey(useLockKey) :
undefined
);
localIdentities[localID] = record;

@@ -338,3 +350,2 @@ cacheLockKey(localID,lockKey);

catch (err) {
console.log(err);
throw new Error("Identity/Passkey registration failed",{ cause: err, });

@@ -384,2 +395,3 @@ }

return {
keyFormatVersion: CURRENT_LOCK_KEY_FORMAT_VERSION,
iv,

@@ -401,2 +413,22 @@ publicKey: ed25519KeyPair.publicKey,

function checkLockKey(lockKeyCandidate) {
if (
lockKeyCandidate &&
typeof lockKeyCandidate == "object"
) {
// assume current format key?
if (lockKeyCandidate.keyFormatVersion === CURRENT_LOCK_KEY_FORMAT_VERSION) {
return lockKeyCandidate;
}
// contains a suitable `iv` we can derive from?
else if (
isByteArray(lockKeyCandidate.iv) &&
lockKeyCandidate.iv.byteLength == IV_BYTE_LENGTH
) {
return deriveLockKey(lockKeyCandidate.iv);
}
}
throw new Error("Unrecongnized lock-key");
}
function lockData(

@@ -403,0 +435,0 @@ data,

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc