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

Embedded key fixes #6

Merged
merged 7 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 61 additions & 14 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
font-weight: bold;
text-align: center;
}
#clear {
#reset {
color: white;
width: 7em;
font-size: 1em;
Expand Down Expand Up @@ -80,7 +80,7 @@ <h2>Init Recovery</h2>
<form>
<label>Embedded key</label>
<input type="text" name="embedded-key" id="embedded-key" disabled/>
<button id="clear">Reset Key</button>
<button id="reset">Reset Key</button>
</form>
<br>
<br>
Expand Down Expand Up @@ -116,7 +116,8 @@ <h2>Message log</h2>
<script>
window.TKHQ = function() {
/** constant for LocalStorage */
var TURNKEY_EMBEDDED_KEY = "TURNKEY_EMBEDDED_KEY"
var TURNKEY_EMBEDDED_KEY = "TURNKEY_EMBEDDED_KEY";
var TURNKEY_EMBEDDED_KEY_TTL_IN_MILLIS = 1000 * 60 * 60 * 48; // 48 hours in milliseconds

/**
* Creates a new public/private key pair and persists it in localStorage
Expand All @@ -136,9 +137,9 @@ <h2>Message log</h2>
*/
var generateTargetKey = async function() {
var p256key = await crypto.subtle.generateKey({
name: 'ECDSA',
name: 'ECDH',
namedCurve: 'P-256',
}, true, ['sign',]);
}, true, ['deriveBits',]);
oliviathet marked this conversation as resolved.
Show resolved Hide resolved

return await crypto.subtle.exportKey("jwk", p256key.privateKey);
}
Expand All @@ -147,7 +148,7 @@ <h2>Message log</h2>
* Gets the current embedded private key JWK. Returns `null` if not found.
*/
var getEmbeddedKey = function() {
var jwtKey = window.localStorage.getItem(TURNKEY_EMBEDDED_KEY)
var jwtKey = getItemWithExpiry(TURNKEY_EMBEDDED_KEY)
if (!jwtKey) {
return null
} else {
Expand All @@ -156,13 +157,55 @@ <h2>Message log</h2>
}

var setEmbeddedKey = function(targetKey) {
return window.localStorage.setItem(TURNKEY_EMBEDDED_KEY, JSON.stringify(targetKey))
return setItemWithExpiry(TURNKEY_EMBEDDED_KEY, JSON.stringify(targetKey), TURNKEY_EMBEDDED_KEY_TTL_IN_MILLIS);
}

var clearEmbeddedKey = function() {
var onResetEmbeddedKey = function() {
window.localStorage.removeItem(TURNKEY_EMBEDDED_KEY)
}

/**
* Set an item in localStorage with an expiration time
* @param {string} key
* @param {string} value
* @param {number} ttl expiration time in milliseconds
*/
var setItemWithExpiry = function(key, value, ttl) {
const now = new Date();
const item = {
value: value,
expiry: now.getTime() + ttl,
r-n-o marked this conversation as resolved.
Show resolved Hide resolved
};
window.localStorage.setItem(key, JSON.stringify(item));
};

/**
* Get an item from localStorage. If it has expired, remove
* the item from localStorage and return null.
* @param {string} key
*/
const getItemWithExpiry = (key) => {
const itemStr = window.localStorage.getItem(key);

if (!itemStr) {
return null;
}

const item = JSON.parse(itemStr);

if (!item.hasOwnProperty("expiry") || !item.hasOwnProperty("value")) {
window.localStorage.removeItem(key);
return null;
}

const now = new Date();
if (now.getTime() > item.expiry) {
window.localStorage.removeItem(key);
return null;
}
return item.value;
};

/**
* Takes a hex string (e.g. "e4567ab") and returns an array buffer (Uint8Array)
* @param {string} hexString
Expand Down Expand Up @@ -707,9 +750,10 @@ <h2>Message log</h2>
return {
initEmbeddedKey,
generateTargetKey,
setItemWithExpiry,
getItemWithExpiry,
getEmbeddedKey,
setEmbeddedKey,
clearEmbeddedKey,
importRecoveryCredential,
compressRawPublicKey,
uncompressRawPublicKey,
Expand Down Expand Up @@ -759,6 +803,10 @@ <h2>Message log</h2>
TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}: ${event.data["value"]}`);
onStampRequest(event.data["value"]);
}
if (event.data && event.data["type"] == "RESET_EMBEDDED_KEY") {
TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}`);
onResetEmbeddedKey();
}
}, false);

/**
Expand All @@ -780,11 +828,10 @@ <h2>Message log</h2>
"value": document.getElementById("payload").value,
})
}, false);

/**
* DEBUG functionality only to clear the page's localStorage
*/
document.getElementById("clear").addEventListener("click", TKHQ.clearEmbeddedKey);
document.getElementById("reset").addEventListener("click", async function(e) {
e.preventDefault();
window.postMessage({ "type": "RESET_EMBEDDED_KEY" })
}, false);
}, false);

/**
Expand Down
31 changes: 26 additions & 5 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,36 @@ describe("TKHQ", () => {
TKHQ = dom.window.TKHQ
})

it("gets, sets, and removes values in localStorage", async () => {
it("gets and sets items with expiry localStorage", async () => {
oliviathet marked this conversation as resolved.
Show resolved Hide resolved
// Set a TTL of 1000ms
TKHQ.setItemWithExpiry("k", "v", 1000);
let item = JSON.parse(dom.window.localStorage.getItem("k"));
expect(item.value).toBe("v");
expect(item.expiry).toBeTruthy();

// Get item that has not expired yet
item = TKHQ.getItemWithExpiry("k");
expect(item).toBe("v");

// Set a TTL of 500ms
TKHQ.setItemWithExpiry("a", "b", 500);
setTimeout(() => {
const expiredItem = TKHQ.getItemWithExpiry("a");
expect(expiredItem).toBeNull();
}, 600); // Wait for 600ms to ensure the item has expired

// Returns null if getItemWithExpiry is called for item without expiry
dom.window.localStorage.setItem("k", JSON.stringify({ value: "v" }));
item = TKHQ.getItemWithExpiry("k");
expect(item).toBeNull();
})

it("gets and sets embedded key in localStorage", async () => {
expect(TKHQ.getEmbeddedKey()).toBe(null);

// Set a dummy "key"
TKHQ.setEmbeddedKey({"foo": "bar"});
expect(TKHQ.getEmbeddedKey()).toEqual({"foo": "bar"});

// Now clear and assert we're back to no embedded key
TKHQ.clearEmbeddedKey();
expect(TKHQ.getEmbeddedKey()).toBe(null);
})

it("inits embedded key and is idempotent", async () => {
Expand All @@ -56,6 +76,7 @@ describe("TKHQ", () => {
expect(key.kty).toEqual("EC");
expect(key.ext).toBe(true);
expect(key.crv).toBe("P-256");
expect(key.key_ops).toContain("deriveBits");
})

it("imports recovery credentials without errors", async () => {
Expand Down
13 changes: 6 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading