Skip to content

Commit

Permalink
Merge pull request #186 from 1Password/secure-storage-polyfill
Browse files Browse the repository at this point in the history
Add mock for browser.secureStorage proposal
  • Loading branch information
dotproto authored Jul 7, 2022
2 parents 724dac1 + e5d31e6 commit ed94d59
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
97 changes: 97 additions & 0 deletions proposals/secure-storage-mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
let global;

if (globalThis.browser?.runtime.id) {
global = browser;
} else if (globalThis.chrome?.runtime.id) {
global = chrome;
} else {
throw new Error(
"browser.secureStorage mock must be run in extension contexts"
);
}

if (!global.storage?.local) {
throw new Error("Using this mock requires the 'storage' permission");
}

// Existing browser APIs don't give us access to the system's secure storage,
// meaning all data in this mock is stored in less secure mechanisms.
console.warn(
"Warning: browser.secureStorage mock loaded. This proof of concept stores data insecurely and should not be used in production."
);

const RECOGNISED_AUTH_METHODS = [
"PIN",
"PASSWORD",
"BIOMETRY_FACE",
"BIOMETRY_FINGERPRINT",
];

const secureStorage = {
getInfo: async () => {
return {
type: "MACOS_KEYCHAIN",
availableAuthentication: RECOGNISED_AUTH_METHODS,
};
},
store: async (request) => {
if (typeof request !== "object")
throw new Error("secureStorage.store takes an object");

const { id, authentication, data } = request;

if (typeof id !== "string") throw new Error("id must be a string");

if (typeof authentication !== "undefined") {
if (!Array.isArray(authentication))
throw new Error("authentication must be an array");

if (authentication.length === 0)
throw new Error("authentication array must be non-empty if present");

for (const method of authentication) {
if (!RECOGNISED_AUTH_METHODS.includes(method)) {
throw new Error(`unrecognised auth method: ${method}`);
}
}
}

if (typeof data !== "string") throw new Error("data must be a string");

return new Promise((resolve) =>
global.storage.local.set({ [id]: data }, resolve)
);
},
retrieve: async (request) => {
if (typeof request !== "object")
throw new Error("secureStorage.retrieve takes an object");

const { id } = request;

if (typeof id !== "string") throw new Error("id must be a string");

return new Promise((resolve) => {
global.storage.local.get(id, (result) => resolve(result[id]));
});
},
remove: async (request) => {
if (typeof request !== "object")
throw new Error("secureStorage.remove takes an object");

const { id } = request;

if (typeof id !== "string") throw new Error("id must be a string");

return new Promise((resolve) => global.storage.local.remove(id, resolve));
},
};

// Attach to browser namespace in Firefox/Safari
if (globalThis.browser?.runtime?.id) {
browser.secureStorage = secureStorage;
}

// Attach to chrome namespace in all browsers
if (globalThis.chrome?.runtime?.id) {
chrome.secureStorage = secureStorage;
}
4 changes: 4 additions & 0 deletions proposals/secure-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ We propose a new browser.secureStorage API that would use platform-dependent API
- Android: [Keystore](https://source.android.com/security/keystore)
- Linux: See FAQ

A mock for this proposal is available [here](secure-storage-mock.js).

### API

**browser.secureStorage.getInfo**
Expand Down Expand Up @@ -59,6 +61,8 @@ browser.secureStorage.store({
});
```

The authentication array is optional. If omitted, the secret is available without the need for any of the recognised auth methods but is still stored in the hardware backed location.

**browser.secureStorage.retrieve**

This retrieves the stored data. The browser will only provide it if the user authenticates with one of the allowed mechanisms for this secret, and will throw an error otherwise.
Expand Down

0 comments on commit ed94d59

Please sign in to comment.