Skip to content

Commit

Permalink
Merge pull request #6 from tkhq/olivia/expire-and-fix
Browse files Browse the repository at this point in the history
Embedded key fixes
  • Loading branch information
Olivia Thet authored Oct 19, 2023
2 parents ce33591 + 75f52ef commit 6b6d7b9
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 26 deletions.
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',]);

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,
};
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 () => {
// 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.

0 comments on commit 6b6d7b9

Please sign in to comment.