diff --git a/README.md b/README.md
index 286402d..3fba0e0 100644
--- a/README.md
+++ b/README.md
@@ -22,4 +22,15 @@ $ npm run watch
# Loading into the browser
-You can load the project as an unpacked extension. Upon building, you may load the directory `dist/chromium/` into your browser. More details on how to do this [here](https://developer.chrome.com/extensions/getstarted).
\ No newline at end of file
+You can load the project as an unpacked extension. Upon building, you may load the directory `dist/chromium/` into your browser. More details on how to do this [here](https://developer.chrome.com/extensions/getstarted).
+
+# PSK Protocol Support
+
+- Support for PSK WebAuthn Extension
+- Support for PSK Setup API
+
+# OS Support
+
+The extension was tested with Ubuntu 18.04 and macOS 10.15.7
+
+Windows 10 does not use the default Chrome popup to search for available authenticators, but uses a Windows Security popup that unfortunately disables the Chrome window until a certain timeout. Once the timeout is reached and the Chrome browser window becomes active again, you can interact the cKey extension.
diff --git a/dist/chromium/manifest.json b/dist/chromium/manifest.json
index 744560f..c7a85d5 100644
--- a/dist/chromium/manifest.json
+++ b/dist/chromium/manifest.json
@@ -2,13 +2,14 @@
"manifest_version": 2,
"name": "CKey",
"description": "A Chrome Extension that emulates a Hardware Authentication Device",
- "version": "1.0.2",
+ "version": "1.0.4",
"minimum_chrome_version": "36.0.1985.18",
"content_scripts": [
{
"all_frames": true,
"matches": [
- "https://*/*"
+ "https://*/*",
+ "http://localhost/*"
],
"exclude_matches": [
"https://*/*.xml"
@@ -26,7 +27,12 @@
"js/background.js"
]
},
- "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
+ "options_page": "options.html",
+ "options_ui": {
+ "page": "options.html",
+ "open_in_tab": false
+ },
+ "content_security_policy": "script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://stackpath.bootstrapcdn.com 'unsafe-eval'; object-src 'self'",
"page_action": {
"default_icon": {
"16": "images/lock-16.png",
diff --git a/dist/chromium/options.html b/dist/chromium/options.html
new file mode 100644
index 0000000..25c9c95
--- /dev/null
+++ b/dist/chromium/options.html
@@ -0,0 +1,50 @@
+
+
+
+
+ Identity Manager
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PSK Options
+
+ Set PIN
+ Sync
+
+
+
+ Backup Device Contact URL
+
+
+
+ Save
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/chromium/popup.html b/dist/chromium/popup.html
index 29e9940..52bf53e 100644
--- a/dist/chromium/popup.html
+++ b/dist/chromium/popup.html
@@ -9,12 +9,9 @@
diff --git a/dist/chromium/styles/options.css b/dist/chromium/styles/options.css
new file mode 100644
index 0000000..baf1d29
--- /dev/null
+++ b/dist/chromium/styles/options.css
@@ -0,0 +1,111 @@
+/*
+ * Globals
+ */
+
+/* Links */
+a,
+a:focus,
+a:hover {
+ color: #fff;
+}
+
+/* Custom default button */
+.btn-secondary,
+.btn-secondary:hover,
+.btn-secondary:focus {
+ color: #333;
+ text-shadow: none; /* Prevent inheritance from `body` */
+ background-color: #fff;
+ border: .05rem solid #fff;
+}
+
+
+/*
+ * Base structure
+ */
+
+html,
+body {
+ height: 100%;
+ background-color: #333;
+}
+
+body {
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-pack: center;
+ -webkit-box-pack: center;
+ justify-content: center;
+ color: #fff;
+ text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
+ box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
+}
+
+.cover-container {
+ max-width: 42em;
+}
+
+
+/*
+ * Header
+ */
+.masthead {
+ margin-bottom: 2rem;
+}
+
+.masthead-brand {
+ margin-bottom: 0;
+}
+
+.nav-masthead .nav-link {
+ padding: .25rem 0;
+ font-weight: 700;
+ color: rgba(255, 255, 255, .5);
+ background-color: transparent;
+ border-bottom: .25rem solid transparent;
+}
+
+.nav-masthead .nav-link:hover,
+.nav-masthead .nav-link:focus {
+ border-bottom-color: rgba(255, 255, 255, .25);
+}
+
+.nav-masthead .nav-link + .nav-link {
+ margin-left: 1rem;
+}
+
+.nav-masthead .active {
+ color: #fff;
+ border-bottom-color: #fff;
+}
+
+@media (min-width: 250px) {
+ .masthead-brand {
+ float: left;
+ }
+ .nav-masthead {
+ float: right;
+ }
+}
+
+
+/*
+ * Cover
+ */
+.cover {
+ padding: 0 1.5rem;
+}
+.cover .btn-lg {
+ padding: .75rem 1.25rem;
+ font-weight: 700;
+}
+
+
+/*
+ * Footer
+ */
+.mastfoot {
+ color: rgba(255, 255, 255, .5);
+}
+
diff --git a/package-lock.json b/package-lock.json
index d3ae219..ce6174a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "ckey",
- "version": "1.0.1",
+ "version": "1.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -69,9 +69,9 @@
"dev": true
},
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"ms": {
@@ -310,13 +310,34 @@
},
"dependencies": {
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
}
}
},
+ "@fidm/asn1": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@fidm/asn1/-/asn1-1.0.4.tgz",
+ "integrity": "sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ=="
+ },
+ "@fidm/x509": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@fidm/x509/-/x509-1.2.1.tgz",
+ "integrity": "sha512-nwc2iesjyc9hkuzcrMCBXQRn653XuAUKorfWM8PZyJawiy1QzLj4vahwzaI25+pfpwOLvMzbJ0uKpWLDNmo16w==",
+ "requires": {
+ "@fidm/asn1": "^1.0.4",
+ "tweetnacl": "^1.0.1"
+ },
+ "dependencies": {
+ "tweetnacl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
+ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
+ }
+ }
+ },
"@jest/console": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
@@ -818,178 +839,177 @@
"dev": true
},
"@webassemblyjs/ast": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
- "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
+ "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
"dev": true,
"requires": {
- "@webassemblyjs/helper-module-context": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/wast-parser": "1.8.5"
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0"
}
},
"@webassemblyjs/floating-point-hex-parser": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz",
- "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz",
+ "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==",
"dev": true
},
"@webassemblyjs/helper-api-error": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz",
- "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz",
+ "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==",
"dev": true
},
"@webassemblyjs/helper-buffer": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz",
- "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz",
+ "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==",
"dev": true
},
"@webassemblyjs/helper-code-frame": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz",
- "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz",
+ "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==",
"dev": true,
"requires": {
- "@webassemblyjs/wast-printer": "1.8.5"
+ "@webassemblyjs/wast-printer": "1.9.0"
}
},
"@webassemblyjs/helper-fsm": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz",
- "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz",
+ "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==",
"dev": true
},
"@webassemblyjs/helper-module-context": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
- "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz",
+ "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "mamacro": "^0.0.3"
+ "@webassemblyjs/ast": "1.9.0"
}
},
"@webassemblyjs/helper-wasm-bytecode": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz",
- "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz",
+ "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==",
"dev": true
},
"@webassemblyjs/helper-wasm-section": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz",
- "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz",
+ "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-buffer": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/wasm-gen": "1.8.5"
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0"
}
},
"@webassemblyjs/ieee754": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz",
- "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz",
+ "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==",
"dev": true,
"requires": {
"@xtuc/ieee754": "^1.2.0"
}
},
"@webassemblyjs/leb128": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz",
- "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz",
+ "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==",
"dev": true,
"requires": {
"@xtuc/long": "4.2.2"
}
},
"@webassemblyjs/utf8": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz",
- "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz",
+ "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==",
"dev": true
},
"@webassemblyjs/wasm-edit": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz",
- "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz",
+ "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-buffer": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/helper-wasm-section": "1.8.5",
- "@webassemblyjs/wasm-gen": "1.8.5",
- "@webassemblyjs/wasm-opt": "1.8.5",
- "@webassemblyjs/wasm-parser": "1.8.5",
- "@webassemblyjs/wast-printer": "1.8.5"
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/helper-wasm-section": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0",
+ "@webassemblyjs/wasm-opt": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0",
+ "@webassemblyjs/wast-printer": "1.9.0"
}
},
"@webassemblyjs/wasm-gen": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz",
- "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz",
+ "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/ieee754": "1.8.5",
- "@webassemblyjs/leb128": "1.8.5",
- "@webassemblyjs/utf8": "1.8.5"
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/ieee754": "1.9.0",
+ "@webassemblyjs/leb128": "1.9.0",
+ "@webassemblyjs/utf8": "1.9.0"
}
},
"@webassemblyjs/wasm-opt": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz",
- "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz",
+ "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-buffer": "1.8.5",
- "@webassemblyjs/wasm-gen": "1.8.5",
- "@webassemblyjs/wasm-parser": "1.8.5"
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0"
}
},
"@webassemblyjs/wasm-parser": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz",
- "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz",
+ "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-api-error": "1.8.5",
- "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
- "@webassemblyjs/ieee754": "1.8.5",
- "@webassemblyjs/leb128": "1.8.5",
- "@webassemblyjs/utf8": "1.8.5"
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-api-error": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/ieee754": "1.9.0",
+ "@webassemblyjs/leb128": "1.9.0",
+ "@webassemblyjs/utf8": "1.9.0"
}
},
"@webassemblyjs/wast-parser": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz",
- "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz",
+ "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/floating-point-hex-parser": "1.8.5",
- "@webassemblyjs/helper-api-error": "1.8.5",
- "@webassemblyjs/helper-code-frame": "1.8.5",
- "@webassemblyjs/helper-fsm": "1.8.5",
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/floating-point-hex-parser": "1.9.0",
+ "@webassemblyjs/helper-api-error": "1.9.0",
+ "@webassemblyjs/helper-code-frame": "1.9.0",
+ "@webassemblyjs/helper-fsm": "1.9.0",
"@xtuc/long": "4.2.2"
}
},
"@webassemblyjs/wast-printer": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz",
- "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz",
+ "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/wast-parser": "1.8.5",
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0",
"@xtuc/long": "4.2.2"
}
},
@@ -1028,9 +1048,9 @@
},
"dependencies": {
"acorn": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz",
- "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==",
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"dev": true
}
}
@@ -1060,9 +1080,9 @@
"dev": true
},
"ajv-keywords": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
- "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.0.tgz",
+ "integrity": "sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==",
"dev": true
},
"ansi-colors": {
@@ -1242,6 +1262,22 @@
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "asn1js": {
+ "version": "2.0.26",
+ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.26.tgz",
+ "integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==",
+ "requires": {
+ "pvutils": "^1.0.17"
}
},
"assert": {
@@ -1293,7 +1329,8 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
"integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"async-limiter": {
"version": "1.0.1",
@@ -1325,6 +1362,14 @@
"integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==",
"dev": true
},
+ "axios": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "requires": {
+ "follow-redirects": "1.5.10"
+ }
+ },
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -2178,6 +2223,11 @@
"tweetnacl": "^0.14.3"
}
},
+ "bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
+ },
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -2190,10 +2240,11 @@
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
},
"binary-extensions": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
- "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
- "dev": true
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+ "dev": true,
+ "optional": true
},
"bindings": {
"version": "1.5.0",
@@ -2247,10 +2298,9 @@
"dev": true
},
"bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
- "dev": true
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz",
+ "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA=="
},
"brace-expansion": {
"version": "1.1.11",
@@ -2283,8 +2333,7 @@
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
- "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
- "dev": true
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
},
"browser-process-hrtime": {
"version": "0.1.3",
@@ -2360,21 +2409,56 @@
"requires": {
"bn.js": "^4.1.0",
"randombytes": "^2.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
}
},
"browserify-sign": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
- "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
- "dev": true,
- "requires": {
- "bn.js": "^4.1.1",
- "browserify-rsa": "^4.0.0",
- "create-hash": "^1.1.0",
- "create-hmac": "^1.1.2",
- "elliptic": "^6.0.0",
- "inherits": "^2.0.1",
- "parse-asn1": "^5.0.0"
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz",
+ "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^5.1.1",
+ "browserify-rsa": "^4.0.1",
+ "create-hash": "^1.2.0",
+ "create-hmac": "^1.1.7",
+ "elliptic": "^6.5.2",
+ "inherits": "^2.0.4",
+ "parse-asn1": "^5.1.5",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
}
},
"browserify-zlib": {
@@ -2456,9 +2540,9 @@
"dev": true
},
"cacache": {
- "version": "12.0.3",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
- "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
+ "version": "12.0.4",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+ "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
"dev": true,
"requires": {
"bluebird": "^3.5.5",
@@ -2569,37 +2653,90 @@
}
},
"chokidar": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
- "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
+ "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
"dev": true,
+ "optional": true,
"requires": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "fsevents": "^1.2.7",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.1.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.4.0"
},
"dependencies": {
+ "anymatch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "optional": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "optional": true
+ },
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true
+ "dev": true,
+ "optional": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
}
}
},
"chownr": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
- "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
},
"chrome-trace-event": {
@@ -2887,6 +3024,14 @@
"requires": {
"bn.js": "^4.1.0",
"elliptic": "^6.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
}
},
"create-hash": {
@@ -3078,9 +3223,9 @@
}
},
"kind-of": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
- "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
@@ -3148,6 +3293,14 @@
"bn.js": "^4.1.0",
"miller-rabin": "^4.0.0",
"randombytes": "^2.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
}
},
"domain-browser": {
@@ -3177,6 +3330,32 @@
"stream-shift": "^1.0.0"
}
},
+ "ec-key": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/ec-key/-/ec-key-0.0.4.tgz",
+ "integrity": "sha512-jJkzm7XWb4Bcu4TpuQnOZD/OGSW3Y35S0Qe0cpnJsricaJtC+Tl3I0OKwgQasxNiEFt0Czv89ncq7vrHSgMj/w==",
+ "requires": {
+ "asn1.js": "^5.2.0"
+ },
+ "dependencies": {
+ "asn1.js": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+ }
+ }
+ },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -3218,10 +3397,9 @@
"dev": true
},
"elliptic": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
- "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
- "dev": true,
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+ "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
@@ -3230,6 +3408,13 @@
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+ }
}
},
"emoji-regex": {
@@ -3591,9 +3776,9 @@
}
},
"figgy-pudding": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
- "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"dev": true
},
"file-uri-to-path": {
@@ -3680,6 +3865,24 @@
"readable-stream": "^2.3.6"
}
},
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -4349,24 +4552,13 @@
}
},
"glob-parent": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"dev": true,
+ "optional": true,
"requires": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- },
- "dependencies": {
- "is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.0"
- }
- }
+ "is-glob": "^4.0.1"
}
},
"global-modules": {
@@ -4390,9 +4582,9 @@
}
},
"kind-of": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
- "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
@@ -4434,26 +4626,6 @@
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
"dev": true
},
- "handlebars": {
- "version": "4.5.3",
- "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
- "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
- "dev": true,
- "requires": {
- "neo-async": "^2.6.0",
- "optimist": "^0.6.1",
- "source-map": "^0.6.1",
- "uglify-js": "^3.1.4"
- },
- "dependencies": {
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
- }
- }
- },
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@@ -4533,20 +4705,45 @@
}
},
"hash-base": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
- "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+ "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"dev": true,
"requires": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
}
},
"hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
- "dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
@@ -4562,7 +4759,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
- "dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
@@ -4603,6 +4799,12 @@
"whatwg-encoding": "^1.0.1"
}
},
+ "html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -4730,8 +4932,7 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
- "dev": true
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
"version": "1.3.5",
@@ -4776,12 +4977,13 @@
"dev": true
},
"is-binary-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
- "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
+ "optional": true,
"requires": {
- "binary-extensions": "^1.0.0"
+ "binary-extensions": "^2.0.0"
}
},
"is-buffer": {
@@ -5085,12 +5287,12 @@
}
},
"istanbul-reports": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz",
- "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==",
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz",
+ "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==",
"dev": true,
"requires": {
- "handlebars": "^4.1.2"
+ "html-escaper": "^2.0.0"
}
},
"iterate-object": {
@@ -5996,9 +6198,9 @@
}
},
"jquery": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
- "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw=="
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
+ "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
},
"js-tokens": {
"version": "3.0.2",
@@ -6120,6 +6322,11 @@
"verror": "1.10.0"
}
},
+ "jsrsasign": {
+ "version": "8.0.20",
+ "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.20.tgz",
+ "integrity": "sha512-JTXt9+nqdynIB8wFsS6e8ffHhIjilhywXwdaEVHSj9OVmwldG2H0EoCqkQ+KXkm2tVqREfH/HEmklY4k1/6Rcg=="
+ },
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
@@ -6240,9 +6447,9 @@
}
},
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
}
}
@@ -6367,12 +6574,6 @@
"tmpl": "1.0.x"
}
},
- "mamacro": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
- "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==",
- "dev": true
- },
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@@ -6476,9 +6677,9 @@
}
},
"kind-of": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
- "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
@@ -6491,6 +6692,14 @@
"requires": {
"bn.js": "^4.0.0",
"brorand": "^1.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
}
},
"mime-db": {
@@ -6517,14 +6726,12 @@
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
- "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
- "dev": true
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.0.4",
@@ -6581,12 +6788,20 @@
}
},
"mkdirp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
- "minimist": "0.0.8"
+ "minimist": "^1.2.5"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ }
}
},
"mocha": {
@@ -6648,6 +6863,15 @@
"path-exists": "^3.0.0"
}
},
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
@@ -6686,6 +6910,16 @@
"requires": {
"has-flag": "^3.0.0"
}
+ },
+ "yargs-parser": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
}
}
},
@@ -6706,8 +6940,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nan": {
"version": "2.14.0",
@@ -6755,9 +6988,9 @@
}
},
"kind-of": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
- "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
@@ -7004,16 +7237,6 @@
"wrappy": "1"
}
},
- "optimist": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
- "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
- "dev": true,
- "requires": {
- "minimist": "~0.0.1",
- "wordwrap": "~0.0.2"
- }
- },
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@@ -7115,9 +7338,9 @@
"dev": true
},
"pako": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
- "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
},
"parallel-transform": {
@@ -7183,7 +7406,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"path-exists": {
"version": "3.0.0",
@@ -7219,9 +7443,9 @@
}
},
"pbkdf2": {
- "version": "3.0.17",
- "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
- "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+ "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
"dev": true,
"requires": {
"create-hash": "^1.1.2",
@@ -7231,12 +7455,24 @@
"sha.js": "^2.4.8"
}
},
+ "pem-file": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pem-file/-/pem-file-1.0.1.tgz",
+ "integrity": "sha512-jIDhaSc4Pk8go+kDYJJ2aS7Bg8Lxvir02NnGp9B1bdJpKiDH680ULl+Duh0jBkz8gV3PywEAWz9XNYqLcd6kVg=="
+ },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
+ "picomatch": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "dev": true,
+ "optional": true
+ },
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
@@ -7366,6 +7602,14 @@
"parse-asn1": "^5.0.0",
"randombytes": "^2.0.1",
"safe-buffer": "^5.1.2"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
}
},
"pump": {
@@ -7407,6 +7651,11 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
+ "pvutils": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz",
+ "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ=="
+ },
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@@ -7538,14 +7787,13 @@
}
},
"readdirp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
- "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+ "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
"dev": true,
+ "optional": true,
"requires": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
+ "picomatch": "^2.2.1"
}
},
"realpath-native": {
@@ -7869,8 +8117,7 @@
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sane": {
"version": "4.1.0",
@@ -7890,9 +8137,9 @@
},
"dependencies": {
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
}
}
@@ -7921,10 +8168,13 @@
"dev": true
},
"serialize-javascript": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
- "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
- "dev": true
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
+ "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
},
"set-blocking": {
"version": "2.0.0",
@@ -8477,9 +8727,9 @@
}
},
"terser": {
- "version": "4.6.3",
- "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz",
- "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+ "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -8500,9 +8750,9 @@
"dev": true
},
"source-map-support": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
- "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
@@ -8512,16 +8762,16 @@
}
},
"terser-webpack-plugin": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
- "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz",
+ "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==",
"dev": true,
"requires": {
"cacache": "^12.0.2",
"find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0",
"schema-utils": "^1.0.0",
- "serialize-javascript": "^2.1.2",
+ "serialize-javascript": "^3.1.0",
"source-map": "^0.6.1",
"terser": "^4.1.2",
"webpack-sources": "^1.4.0",
@@ -8569,9 +8819,9 @@
}
},
"p-limit": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
- "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
@@ -8781,9 +9031,9 @@
}
},
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"yargs-parser": {
@@ -8975,33 +9225,6 @@
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
"dev": true
},
- "uglify-js": {
- "version": "3.7.2",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz",
- "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==",
- "dev": true,
- "optional": true,
- "requires": {
- "commander": "~2.20.3",
- "source-map": "~0.6.1"
- },
- "dependencies": {
- "commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "dev": true,
- "optional": true
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true
- }
- }
- },
"union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -9076,7 +9299,8 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
"integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"uri-js": {
"version": "4.2.2",
@@ -9206,14 +9430,107 @@
}
},
"watchpack": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
- "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
+ "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==",
"dev": true,
"requires": {
- "chokidar": "^2.0.2",
+ "chokidar": "^3.4.0",
"graceful-fs": "^4.1.2",
- "neo-async": "^2.5.0"
+ "neo-async": "^2.5.0",
+ "watchpack-chokidar2": "^2.0.0"
+ }
+ },
+ "watchpack-chokidar2": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz",
+ "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chokidar": "^2.1.8"
+ },
+ "dependencies": {
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+ "dev": true,
+ "optional": true
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "optional": true
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ }
}
},
"web-ext-types": {
@@ -9228,16 +9545,16 @@
"dev": true
},
"webpack": {
- "version": "4.41.5",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.5.tgz",
- "integrity": "sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
+ "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==",
"dev": true,
"requires": {
- "@webassemblyjs/ast": "1.8.5",
- "@webassemblyjs/helper-module-context": "1.8.5",
- "@webassemblyjs/wasm-edit": "1.8.5",
- "@webassemblyjs/wasm-parser": "1.8.5",
- "acorn": "^6.2.1",
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/wasm-edit": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0",
+ "acorn": "^6.4.1",
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1",
"chrome-trace-event": "^1.0.2",
@@ -9248,22 +9565,37 @@
"loader-utils": "^1.2.3",
"memory-fs": "^0.4.1",
"micromatch": "^3.1.10",
- "mkdirp": "^0.5.1",
+ "mkdirp": "^0.5.3",
"neo-async": "^2.6.1",
"node-libs-browser": "^2.2.1",
"schema-utils": "^1.0.0",
"tapable": "^1.1.3",
"terser-webpack-plugin": "^1.4.3",
- "watchpack": "^1.6.0",
+ "watchpack": "^1.6.1",
"webpack-sources": "^1.4.1"
},
"dependencies": {
"acorn": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz",
- "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==",
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@@ -9513,12 +9845,6 @@
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true
},
- "wordwrap": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
- "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
- "dev": true
- },
"worker-farm": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
@@ -9679,9 +10005,9 @@
}
},
"yargs-parser": {
- "version": "13.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
- "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+ "version": "13.1.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
diff --git a/package.json b/package.json
index c78feeb..5cbfa43 100644
--- a/package.json
+++ b/package.json
@@ -19,20 +19,29 @@
"tslint": "^5.20.1",
"tslint-loader": "^3.6.0",
"typescript": "^3.7.5",
- "webpack": "^4.41.5",
+ "webpack": "^4.43.0",
"webpack-cli": "^3.3.10",
"webpack-merge": "^4.2.2",
"zip-folder": "^1.0.0"
},
"dependencies": {
+ "@fidm/x509": "^1.2.1",
"@types/chrome": "^0.0.77",
"@types/jquery": "^3.3.31",
"@types/loglevel": "^1.6.3",
"@types/webappsec-credential-management": "^0.3.11",
+ "asn1js": "^2.0.26",
+ "axios": "^0.19.2",
+ "bcryptjs": "^2.4.3",
+ "bn.js": "^5.1.2",
"cbor": "^4.3.0",
- "jquery": "^3.4.1",
+ "ec-key": "0.0.4",
+ "elliptic": "^6.5.3",
+ "jquery": "^3.5.1",
+ "jsrsasign": "^8.0.20",
"loglevel": "^1.6.6",
"loglevel-plugin-prefix": "^0.8.4",
+ "pem-file": "^1.0.1",
"strip-sourcemap-loader": "^0.0.1",
"web-ext-types": "^3.2.1"
},
@@ -48,4 +57,4 @@
"postrelease": "node scripts/manifest.js",
"release": "node scripts/release.js"
}
-}
\ No newline at end of file
+}
diff --git a/src/background.ts b/src/background.ts
index b378a08..d882b12 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -1,7 +1,12 @@
-import { disabledIcons, enabledIcons } from './constants';
-import { getLogger } from './logging';
-import { getOriginFromUrl, webauthnParse, webauthnStringify } from './utils';
-import { generateKeyRequestAndAttestation, generateRegistrationKeyAndAttestation } from './webauthn';
+import {disabledIcons, enabledIcons} from './constants';
+
+import {getLogger} from './logging';
+
+import {getOriginFromUrl, webauthnParse, webauthnStringify} from './utils';
+
+import {createPublicKeyCredential, getPublicKeyCredential} from "./webauthn_client";
+import {PSK} from "./webauthn_psk";
+import {PinStorage} from "./webauth_storage";
const log = getLogger('background');
@@ -9,103 +14,130 @@ chrome.runtime.onInstalled.addListener(() => {
log.info('Extension installed');
});
-const pinProtectedCallbacks: { [tabId: number]: (pin: number) => void } = {};
+const userConsentCallbacks: { [tabId: number]: (consent: boolean) => void } = {};
-const requestPin = async (tabId: number, origin: string, newPin: boolean = true): Promise => {
+const requestUserConsent = async (tabId: number, origin: string): Promise => {
const tabKey = `tab-${tabId}`;
- chrome.storage.local.set({ [tabKey]: { origin, newPin } }, () => {
+ chrome.storage.local.set({ [tabKey]: { origin } }, () => {
if (chrome.runtime.lastError) {
throw new Error(`failed to store value: ${chrome.runtime.lastError}`);
}
});
log.debug('setting popup for tab', tabId);
- const cb: Promise = new Promise((res, _) => {
- pinProtectedCallbacks[tabId] = res;
+
+ const cb: Promise = new Promise((res, _) => {
+ userConsentCallbacks[tabId] = res;
});
+
chrome.pageAction.setIcon({ tabId, path: enabledIcons });
chrome.pageAction.setPopup({ tabId, popup: 'popup.html' });
chrome.pageAction.show(tabId);
- const pin = await cb;
+ const userConsent = await cb;
chrome.storage.local.remove(tabKey);
chrome.pageAction.setPopup({ tabId, popup: '' });
chrome.pageAction.hide(tabId);
chrome.pageAction.setIcon({ tabId, path: disabledIcons });
- return pin;
+ return userConsent;
};
-const create = async (msg, sender: chrome.runtime.MessageSender) => {
+const createCredential = async (msg, sender: chrome.runtime.MessageSender) => {
if (!sender.tab || !sender.tab.id) {
- log.debug('received create event without a tab ID');
+ log.debug('received createCredential event without a tab ID');
return;
}
-
+ const opts = webauthnParse(msg.options);
const origin = getOriginFromUrl(sender.url);
- const pin = await requestPin(sender.tab.id, origin);
+ const userConsentCB = function() { return requestUserConsent(sender.tab.id, origin); }
try {
- const opts = webauthnParse(msg.options);
- const credential = await generateRegistrationKeyAndAttestation(
+ const credential = await createPublicKeyCredential(
origin,
- opts.publicKey,
- `${pin}`,
+ opts,
+ true,
+ userConsentCB
);
return {
credential: webauthnStringify(credential),
+ clientExtensionResults: credential.getClientExtensionResults(),
requestID: msg.requestID,
- type: 'create_response',
+ type: 'create_credential_response',
};
} catch (e) {
- if (e instanceof DOMException) {
- const { code, message, name } = e;
- log.error('failed to import key due to DOMException', { code, message, name }, e);
- } else {
- log.error('failed to import key', { errorType: `${(typeof e)}` }, e);
- }
+ log.error('failed to register credential', { errorType: `${(typeof e)}` }, e);
}
};
-const sign = async (msg, sender: chrome.runtime.MessageSender) => {
+const getCredential = async (msg, sender: chrome.runtime.MessageSender) => {
+ if (!sender.tab || !sender.tab.id) {
+ log.debug('received getCredential event without a tab ID');
+ return;
+ }
const opts = webauthnParse(msg.options);
- const pin = await requestPin(sender.tab.id, origin);
+ const origin = getOriginFromUrl(sender.url);
+ const userConsentCB = function() { return requestUserConsent(sender.tab.id, origin); }
try {
- const credential = await generateKeyRequestAndAttestation(origin, opts.publicKey, `${pin}`);
- const authenticatedResponseData = {
+ const credential = await getPublicKeyCredential(origin, opts, true, userConsentCB);
+ return {
credential: webauthnStringify(credential),
requestID: msg.requestID,
- type: 'sign_response',
+ clientExtensionResults: credential.getClientExtensionResults(),
+ type: 'get_credential_response',
};
- return authenticatedResponseData;
} catch (e) {
- if (e instanceof DOMException) {
- const { code, message, name } = e;
- log.error('failed to sign due DOMException', { code, message, name }, e);
- } else {
- log.error('failed to sign', { errorType: `${(typeof e)}` }, e);
- }
+ log.error('failed to create credential assertion', { errorType: `${(typeof e)}` }, e);
}
};
+const pskSync = async () => {
+ await PSK.pskSetup();
+};
+
+const pskOptions = async (alias, url) => {
+ await PSK.setOptions(alias, url);
+};
+
+const authSetup = async () => {
+ let pin = await PinStorage.getPinHash().catch(_ => null);
+ if (pin != null) {
+ throw new Error("PIN already set");
+ }
+ pin = prompt("Please enter a PIN for the authenticator", "");
+ if (pin == null) {
+ throw new Error("Invalid PIN");
+ }
+ return await PinStorage.setPin(pin);
+}
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
switch (msg.type) {
- case 'create':
- create(msg, sender).then(sendResponse);
+ case 'create_credential':
+ createCredential(msg, sender).then(sendResponse);
+ break;
+ case 'get_credential':
+ getCredential(msg, sender).then(sendResponse);
+ break;
+ case 'psk_setup':
+ pskSync().then(() => {}, e => log.error('PSK setup flow failed', { errorType: `${(typeof e)}` }, e));
break;
- case 'sign':
- sign(msg, sender).then(sendResponse);
+ case 'psk_options':
+ pskOptions(msg.alias, msg.url).then(() => alert('PSK options set successfully.'), e => log.error('failed to set psk options', { errorType: `${(typeof e)}` }, e));
break;
- case 'pin':
- const cb = pinProtectedCallbacks[msg.tabId];
+ case 'auth_pin_set':
+ authSetup().then(() => alert('Authenticator PIN setup was successful.'), e => alert(e));
+ break;
+ case 'user_consent':
+ const cb = userConsentCallbacks[msg.tabId];
if (!cb) {
- log.warn(`Received pin for tab ${msg.tabId} but no callback registered`);
+ log.warn(`Received user consent for tab ${msg.tabId} but no callback registered`);
} else {
- cb(msg.pin);
- delete (pinProtectedCallbacks[msg.tabId]);
+ cb(msg.userConsent);
+ delete (userConsentCallbacks[msg.tabId]);
}
break;
+
default:
sendResponse(null);
}
-
return true;
});
diff --git a/src/constants.ts b/src/constants.ts
index aaa403d..b9bd5e4 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -15,3 +15,16 @@ export const enabledIcons = {
48: 'images/lock_enabled-48.png',
128: 'images/lock_enabled-128.png',
};
+
+export const ES256_COSE = -7;
+export const ES256 = 'P-256';
+export const SHA256_COSE = 1;
+
+export const PIN = 'pin';
+export const PSK_EXTENSION_IDENTIFIER = 'PSK';
+export const BACKUP_KEY = 'backup_key';
+export const BD_ENDPOINT = 'bd_endpoint';
+export const DEFAULT_BD_ENDPOINT = 'http://localhost:8005';
+export const RECOVERY_KEY = 'recovery_key';
+export const BD_TIMEOUT = 60 * 1000 * 10; // 10 minutes
+export const BD = 'bd'
diff --git a/src/content_script.ts b/src/content_script.ts
index d557263..2cd4493 100644
--- a/src/content_script.ts
+++ b/src/content_script.ts
@@ -5,7 +5,7 @@ webauthnInject.type = 'text/javascript';
webauthnInject.src = 'chrome-extension://' + chrome.runtime.id + '/js/inject_webauthn.js';
document.documentElement.appendChild(webauthnInject);
-const relevantEventTypes = ['create', 'sign'];
+const relevantEventTypes = ['create_credential', 'get_credential'];
window.addEventListener('message', (event) => {
// We only accept messages from this window to itself, no iframes allowed.
@@ -16,8 +16,7 @@ window.addEventListener('message', (event) => {
// Relay relevant messages only.
if (event.data.type && relevantEventTypes.indexOf(event.data.type) > -1) {
chrome.runtime.sendMessage(event.data, (resp: any) => {
- // The callback function will relay the extension response
- // to the window object.
+ // The callback function will relay the extension response to the window object.
window.postMessage({
requestID: resp.requestID,
resp,
diff --git a/src/crypto.ts b/src/crypto.ts
deleted file mode 100644
index 2b2cef0..0000000
--- a/src/crypto.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import * as CBOR from 'cbor';
-import { getLogger } from './logging';
-import { base64ToByteArray, byteArrayToBase64 } from './utils';
-
-const log = getLogger('crypto');
-
-// Generated with pseudo random values via
-// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
-export const CKEY_ID = new Uint8Array([
- 194547236, 76082241, 3628762690, 4137210381,
- 1214244733, 1205845608, 840015201, 3897052717,
- 4072880437, 4027233456, 675224361, 2305433287,
- 74291263, 3461796691, 701523034, 3178201666,
- 3992003567, 1410532, 4234129691, 1438515639,
-]);
-
-// Copied from krypton
-function counterToBytes(c: number): Uint8Array {
- const bytes = new Uint8Array(4);
- // Sadly, JS TypedArrays are whatever-endian the platform is,
- // so Uint32Array is not at all useful here (or anywhere?),
- // and we must manually pack the counter (big endian as per spec).
- bytes[0] = 0xFF & c >>> 24;
- bytes[1] = 0xFF & c >>> 16;
- bytes[2] = 0xFF & c >>> 8;
- bytes[3] = 0xFF & c;
- return bytes;
-}
-
-const coseEllipticCurveNames: { [s: number]: string } = {
- 1: 'SHA-256',
- 2: 'SHA-384',
- 3: 'SHA-512',
-};
-
-const ellipticNamedCurvesToCOSE: { [s: string]: number } = {
- 'P-256': -7,
- 'P-384': -35,
- 'P-512': -36,
-};
-
-interface ICOSECompatibleKey {
- algorithm: number;
- privateKey: CryptoKey;
- publicKey?: CryptoKey;
- generateClientData(challenge: ArrayBuffer, extraOptions: any): Promise;
- generateAuthenticatorData(rpID: string, counter: number): Promise;
- sign(clientData: string): Promise;
-}
-
-class ECDSA implements ICOSECompatibleKey {
-
- public static async fromKey(key: CryptoKey): Promise {
- return new ECDSA(ellipticNamedCurvesToCOSE[(key.algorithm as EcKeyAlgorithm).namedCurve], key);
- }
-
- public static async fromCOSEAlgorithm(algorithm: number): Promise {
- // Creating the key
- let namedCurve: string;
- for (const k in ellipticNamedCurvesToCOSE) {
- if (ellipticNamedCurvesToCOSE[k] === algorithm) {
- namedCurve = k;
- break;
- }
- }
- if (!namedCurve) {
- throw new Error(`could not find a named curve for algorithm ${algorithm}`);
- }
- const keyPair = await window.crypto.subtle.generateKey(
- { name: 'ECDSA', namedCurve },
- true,
- ['sign'],
- );
- return new ECDSA(algorithm, keyPair.privateKey, keyPair.publicKey);
- }
-
- /**
- * This maps a COSE algorithm ID https://www.iana.org/assignments/cose/cose.xhtml#algorithms
- * to its respective COSE curve ID // Based on https://tools.ietf.org/html/rfc8152#section-13.1.
- */
- private static ellipticCurveKeys: { [s: number]: number } = {
- [-7]: 1,
- [-35]: 2,
- [-36]: 3,
- };
-
- constructor(
- public algorithm: number,
- public privateKey: CryptoKey,
- public publicKey?: CryptoKey,
- ) {
- if (!(algorithm in ECDSA.ellipticCurveKeys)) {
- throw new Error(`unknown ECDSA algorithm ${algorithm}`);
- }
- }
-
- public async generateClientData(challenge: ArrayBuffer, extraOptions: any): Promise {
- return JSON.stringify({
- challenge: byteArrayToBase64(Buffer.from(challenge), true),
- hashAlgorithm: coseEllipticCurveNames[ECDSA.ellipticCurveKeys[this.algorithm]],
- ...extraOptions,
- });
- }
-
- public async generateAuthenticatorData(rpID: string, counter: number): Promise {
- const rpIdDigest = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(rpID));
- const rpIdHash = new Uint8Array(rpIdDigest);
-
- // CKEY_ID is a HAD-specific ID
- let aaguid: Uint8Array;
- let credIdLen: Uint8Array;
- let encodedKey: Uint8Array;
-
- let authenticatorDataLength = rpIdHash.length + 1 + 4;
- if (this.publicKey) {
- aaguid = CKEY_ID.slice(0, 16);
- // 16-bit unsigned big-endian integer.
- credIdLen = new Uint8Array(2);
- credIdLen[0] = (CKEY_ID.length >> 8) & 0xff;
- credIdLen[1] = CKEY_ID.length & 0xff;
- const coseKey = await this.toCOSE(this.publicKey);
- encodedKey = new Uint8Array(CBOR.encode(coseKey));
- authenticatorDataLength += aaguid.length
- + credIdLen.byteLength
- + CKEY_ID.length
- + encodedKey.byteLength;
- }
-
- const authenticatorData = new Uint8Array(authenticatorDataLength);
- let offset = 0;
-
- // 32 bytes for the RP ID hash
- authenticatorData.set(rpIdHash, 0);
- offset += rpIdHash.length;
-
- // 1 byte for flags
- // user-presence flag goes on the right-most bit
- authenticatorData[rpIdHash.length] = 1;
- if (this.publicKey) {
- // attestation flag goes on the 7th bit (from the right)
- authenticatorData[rpIdHash.length] |= (1 << 6);
- offset++;
- }
-
- // 4 bytes for the counter. big-endian uint32
- // https://www.w3.org/TR/webauthn/#signature-counter
- authenticatorData.set(counterToBytes(counter), offset);
- offset += counterToBytes(counter).length;
-
- if (!this.publicKey) {
- return authenticatorData;
- }
-
- // 16 bytes for the Authenticator Attestation GUID
- authenticatorData.set(aaguid, offset);
- offset += aaguid.length;
-
- // 2 bytes for the authenticator key ID length. 16-bit unsigned big-endian integer.
- authenticatorData.set(credIdLen, offset);
- offset += credIdLen.byteLength;
-
- // Variable length authenticator key ID
- authenticatorData.set(CKEY_ID, offset);
- offset += CKEY_ID.length;
-
- // Variable length public key
- authenticatorData.set(encodedKey, offset);
-
- return authenticatorData;
- }
-
- public async sign(data: string): Promise {
- if (!this.privateKey) {
- throw new Error('no private key available for signing');
- }
- return window.crypto.subtle.sign(
- this.getKeyParams(),
- this.privateKey,
- new TextEncoder().encode(data),
- );
- }
-
- private getKeyParams(): EcdsaParams {
- return { name: 'ECDSA', hash: coseEllipticCurveNames[ECDSA.ellipticCurveKeys[this.algorithm]] };
- }
-
- private async toCOSE(key: CryptoKey): Promise> {
- // In JWK the X and Y portions are Base64URL encoded (https://tools.ietf.org/html/rfc7517#section-3),
- // which is just the right type for COSE encoding (https://tools.ietf.org/html/rfc8152#section-7),
- // we just need to convert it to a byte array.
- const exportedKey = await window.crypto.subtle.exportKey('jwk', key);
- const attData = new Map();
- attData.set(1, 2); // EC2 key type
- attData.set(3, this.algorithm);
- attData.set(-1, ECDSA.ellipticCurveKeys[this.algorithm]);
- attData.set(-2, base64ToByteArray(exportedKey.x, true));
- attData.set(-3, base64ToByteArray(exportedKey.y, true));
- return attData;
- }
-}
-
-// ECDSA w/ SHA-256
-const defaultPKParams = { alg: -7, type: 'public-key' };
-const coseAlgorithmToKeyName = {
- [-7]: 'ECDSA',
- [-35]: 'ECDSA',
- [-36]: 'ECDSA',
-};
-
-export const getCompatibleKey = (pkParams: PublicKeyCredentialParameters[]): Promise => {
- for (const params of (pkParams || [defaultPKParams])) {
- const algorithmName = coseAlgorithmToKeyName[params.alg];
- if (!algorithmName) {
- continue;
- }
- switch (algorithmName) {
- case 'ECDSA':
- return ECDSA.fromCOSEAlgorithm(params.alg);
- default:
- throw new Error(`unsupported key algorithm ${algorithmName}`);
- }
- }
- throw new Error(`unable to get key`);
-};
-
-export const getCompatibleKeyFromCryptoKey = (key: CryptoKey): Promise => {
- switch (key.algorithm.name) {
- case 'ECDSA':
- return ECDSA.fromKey(key);
- default:
- throw new Error(`unsupported key algorithm ${key.algorithm.name}`);
- }
-};
diff --git a/src/inject_webauthn.ts b/src/inject_webauthn.ts
index b35d373..ffc40e7 100644
--- a/src/inject_webauthn.ts
+++ b/src/inject_webauthn.ts
@@ -13,20 +13,20 @@ const log = getLogger('inject_webauthn');
const cKeyCredentials: any = {};
cKeyCredentials.create = async (options: CredentialCreationOptions): Promise => {
const requestID = ++webauthnReqCounter;
- const registerRequest = {
+ const createCredentialRequest = {
options: webauthnStringify(options),
requestID,
- type: 'create',
+ type: 'create_credential',
};
const cb: Promise = new Promise((res, _) => {
webauthnCallbacks[requestID] = res;
});
- window.postMessage(registerRequest, window.location.origin);
+ window.postMessage(createCredentialRequest, window.location.origin);
const webauthnResponse = await cb;
- // Because "options" contains functions we must stringify it, otherwise
- // object cloning is illegal.
+ // Because "options" contains functions we must stringify it, otherwise object cloning is illegal.
const credential = webauthnParse(webauthnResponse.resp.credential);
- credential.getClientExtensionResults = () => ({});
+ credential.getClientExtensionResults = () => (webauthnResponse.resp.clientExtensionResults);
+ // extension result
credential.__proto__ = window['PublicKeyCredential'].prototype;
return credential;
};
@@ -36,20 +36,20 @@ const log = getLogger('inject_webauthn');
webauthnCallbacks[requestID] = res;
});
- const signRequest = {
+ const getCredentialRequest = {
options: webauthnStringify(options),
requestID,
- type: 'sign',
+ type: 'get_credential',
};
- window.postMessage(signRequest, window.location.origin);
+ window.postMessage(getCredentialRequest, window.location.origin);
const webauthnResponse = await cb;
const credential = webauthnParse(webauthnResponse.resp.credential);
- credential.getClientExtensionResults = () => ({});
+ credential.getClientExtensionResults = () => (webauthnResponse.resp.clientExtensionResults);
credential.__proto__ = window['PublicKeyCredential'].prototype;
return credential;
};
- const hybridCredentials = {
+ const hybridCredentials = { // Support native WebAuthn as well as cKey
async create(options) {
log.debug('created called');
const credentialBackends = [
@@ -58,9 +58,7 @@ const log = getLogger('inject_webauthn');
if (nativeCredentials.create) {
credentialBackends.push(nativeCredentials);
}
-
- // We need to bind to the "navigator.credentials" object otherwise
- // the browser will be sad.
+ // Bind to the "navigator.credentials" object
return Promise.race(credentialBackends.map((b) => b.create.bind(navigator.credentials)(options)));
},
async get(options) {
@@ -71,8 +69,7 @@ const log = getLogger('inject_webauthn');
if (nativeCredentials.create) {
credentialBackends.push(nativeCredentials);
}
- // We need to bind to the "navigator.credentials" object otherwise
- // the browser will be sad.
+ // Bind to the "navigator.credentials" object
return Promise.race(credentialBackends.map((b) => b.get.bind(navigator.credentials)(options)));
},
};
@@ -80,7 +77,7 @@ const log = getLogger('inject_webauthn');
Object.assign(navigator.credentials, hybridCredentials);
window.addEventListener('message', (evt) => {
const msg = evt.data;
- if (['create_response', 'sign_response'].indexOf(msg.type) > -1) {
+ if (['create_credential_response', 'get_credential_response'].indexOf(msg.type) > -1) {
log.debug('relevant message', msg);
if (msg.requestID && msg.resp && webauthnCallbacks[msg.requestID]) {
webauthnCallbacks[msg.requestID](msg);
diff --git a/src/options.ts b/src/options.ts
new file mode 100644
index 0000000..cd791e3
--- /dev/null
+++ b/src/options.ts
@@ -0,0 +1,28 @@
+import $ from 'jquery';
+import {PSK} from "./webauthn_psk";
+
+$(() => {
+ $.when(PSK.bdDeviceUrl()).then((url) => $('#BackupDeviceUrl').val(url));
+
+ $('#Sync').on('click', function(evt: Event) {
+ evt.preventDefault();
+ chrome.runtime.sendMessage({
+ type: 'psk_setup',
+ });
+ });
+
+ $('#SaveOptions').on('click', function(evt: Event) {
+ evt.preventDefault();
+ chrome.runtime.sendMessage({
+ type: 'psk_options',
+ url: $('#BackupDeviceUrl').val(),
+ });
+ });
+
+ $('#Pin').on('click', function(evt: Event) {
+ evt.preventDefault();
+ chrome.runtime.sendMessage({
+ type: 'auth_pin_set',
+ });
+ });
+});
diff --git a/src/popup.ts b/src/popup.ts
index 8df0dc7..43055b8 100644
--- a/src/popup.ts
+++ b/src/popup.ts
@@ -9,66 +9,23 @@ $(() => {
if (!currentTab) {
return;
}
- const tabKey = `tab-${currentTab.id}`;
- chrome.storage.local.get([tabKey], (result) => {
- log.debug('got storage results', result);
- const pinPromise: Promise = new Promise((res, _) => {
- $('#domain').text(result[tabKey].origin);
- $('input').first().focus();
- prepareInputs(res);
- });
- pinPromise.then((pin) => {
- log.debug('continue with pin', pin);
- chrome.runtime.sendMessage({
- pin,
- tabId: currentTab.id,
- type: 'pin',
- });
- window.close();
- });
- });
- });
-
- // Inspired by https://codepen.io/nirarazi/pen/ZGovQo
- const prepareInputs = (res: (n: number) => void) => {
- const body = $('body');
- const goToNextInput = (e: JQueryEventObject) => {
- const key = e.which;
- const t = $(e.target);
- const sib = t.next('input');
- if (key !== 9 && (key < 48 || key > 57)) {
- e.preventDefault();
- return false;
- }
- if (key === 9) {
- return true;
- }
- if (!sib || !sib.length) {
- let pin = 0;
- $('input', $('body')).each((n, v) => {
- pin = pin * 10;
- pin += +$(v).val();
- });
- res(pin);
- return false;
- }
- sib.select().focus();
- };
+ $('#userConsent').on('click', function(evt: Event) {
+ evt.preventDefault();
+ evt.stopPropagation();
- const onKeyDown = (e: JQueryEventObject) => {
- const key = e.which;
- if (key === 9 || (key >= 48 && key <= 57)) {
- return true;
- }
- e.preventDefault();
+ chrome.runtime.sendMessage({
+ userConsent: true,
+ tabId: currentTab.id,
+ type: 'user_consent',
+ });
+ window.close();
return false;
- };
-
- const onFocus = (e: JQueryEventObject) => $(e.target).select();
+ });
- body.on('keyup', 'input', goToNextInput);
- body.on('keydown', 'input', onKeyDown);
- body.on('click', 'input', onFocus);
- };
+ const tabKey = `tab-${currentTab.id}`;
+ chrome.storage.local.get([tabKey], (result) => {
+ $('#domain').text(result[tabKey].origin);
+ });
+ });
});
diff --git a/src/storage.ts b/src/storage.ts
deleted file mode 100644
index 4f0519e..0000000
--- a/src/storage.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-import { ivLength, keyExportFormat, saltLength } from './constants';
-import { base64ToByteArray, byteArrayToBase64, concatenate } from './utils';
-
-export const keyExists = (key: string): Promise => {
- return new Promise(async (res, rej) => {
- chrome.storage.sync.get(key, (resp) => {
- if (!!chrome.runtime.lastError) {
- rej(chrome.runtime.lastError);
- } else {
- res(!!resp[key]);
- }
- });
- });
-};
-// function hack() {
-// const keyID = 'V2E2TGQ1RnFqdEJNUVFncG0rUFBxS0UvVTBzcklnTTRVeHhOQWVZU0ZaZz1Ad2ViYXV0aG4ubWU=';
-// chrome.storage.sync.get(keyID, async (resp) => {
-// const raw = resp[keyID];
-// console.log('breaking', raw);
-// console.time();
-// const enc = new TextEncoder();
-// const payload = Uint8Array.from(atob(raw), (c) => c.charCodeAt(0));
-// const saltByteLength = payload[0];
-// const ivByteLength = payload[1];
-// const keyAlgorithmByteLength = payload[2];
-// let offset = 3;
-// const salt = payload.subarray(offset, offset + saltByteLength);
-// offset += saltByteLength;
-// const iv = payload.subarray(offset, offset + ivByteLength);
-// offset += ivByteLength;
-// const keyAlgorithmBytes = payload.subarray(offset, offset + keyAlgorithmByteLength);
-// offset += keyAlgorithmByteLength;
-// const keyBytes = payload.subarray(offset);
-// for (let i = 0; i < 10000; i++) {
-// const pbkdf2Params = {
-// hash: 'SHA-256',
-// iterations: 100000,
-// name: 'PBKDF2',
-// salt,
-// };
-// const derivationKey = await window.crypto.subtle.importKey(
-// 'raw',
-// enc.encode('' + i),
-// { name: 'PBKDF2', length: 256 },
-// false,
-// ['deriveBits', 'deriveKey'],
-// );
-// const wrappingKey = await window.crypto.subtle.deriveKey(
-// pbkdf2Params,
-// derivationKey,
-// { name: 'AES-GCM', length: 256 },
-// true,
-// ['wrapKey', 'unwrapKey'],
-// );
-// const wrapAlgorithm = {
-// iv,
-// name: 'AES-GCM',
-// };
-// const unwrappingKeyAlgorithm = JSON.parse(new TextDecoder().decode(keyAlgorithmBytes));
-// try {
-// const realPrivateKey = await window.crypto.subtle.unwrapKey(
-// 'pkcs8',
-// keyBytes,
-// wrappingKey,
-// wrapAlgorithm,
-// unwrappingKeyAlgorithm,
-// true,
-// ['sign'],
-// );
-// console.log('Success', realPrivateKey, 'in');
-// console.timeEnd();
-// return;
-// } catch (e) {
-// if (i % 100 === 0) {
-// console.log('Testing', i, 'Running for');
-// console.timeLog();
-// }
-// }
-// }
-// });
-// }
-export const deleteKey = (key: string) => {
- return new Promise(async (res, _) => {
- chrome.storage.sync.remove(key);
- res();
- });
-};
-
-const getWrappingKey = async (pin: string, salt: Uint8Array): Promise => {
- const enc = new TextEncoder();
- const derivationKey = await window.crypto.subtle.importKey(
- 'raw',
- enc.encode(pin),
- { name: 'PBKDF2', length: 256 },
- false,
- ['deriveBits', 'deriveKey'],
- );
- const pbkdf2Params: Pbkdf2Params = {
- hash: 'SHA-256',
- iterations: 100000,
- name: 'PBKDF2',
- salt,
- };
- return window.crypto.subtle.deriveKey(
- pbkdf2Params,
- derivationKey,
- { name: 'AES-GCM', length: 256 },
- true,
- ['wrapKey', 'unwrapKey'],
- );
-};
-
-export const fetchKey = async (key: string, pin: string): Promise => {
- return new Promise(async (res, rej) => {
- chrome.storage.sync.get(key, async (resp) => {
- if (!!chrome.runtime.lastError) {
- rej(chrome.runtime.lastError);
- return;
- }
- const payload = base64ToByteArray(resp[key]);
- const saltByteLength = payload[0];
- const ivByteLength = payload[1];
- const keyAlgorithmByteLength = payload[2];
- let offset = 3;
- const salt = payload.subarray(offset, offset + saltByteLength);
- offset += saltByteLength;
- const iv = payload.subarray(offset, offset + ivByteLength);
- offset += ivByteLength;
- const keyAlgorithmBytes = payload.subarray(offset, offset + keyAlgorithmByteLength);
- offset += keyAlgorithmByteLength;
- const keyBytes = payload.subarray(offset);
-
- const wrappingKey = await getWrappingKey(pin, salt);
- const wrapAlgorithm: AesGcmParams = {
- iv,
- name: 'AES-GCM',
- };
- const unwrappingKeyAlgorithm = JSON.parse(new TextDecoder().decode(keyAlgorithmBytes));
- window.crypto.subtle.unwrapKey(
- keyExportFormat,
- keyBytes,
- wrappingKey,
- wrapAlgorithm,
- unwrappingKeyAlgorithm,
- true,
- ['sign'],
- ).then(res, rej);
- });
- });
-};
-
-export const saveKey = (key: string, privateKey: CryptoKey, pin: string): Promise => {
- return new Promise(async (res, rej) => {
- if (!pin) {
- rej('no pin provided');
- return;
- }
- const salt = window.crypto.getRandomValues(new Uint8Array(saltLength));
- const wrappingKey = await getWrappingKey(pin, salt);
- const iv = window.crypto.getRandomValues(new Uint8Array(ivLength));
- const wrapAlgorithm: AesGcmParams = {
- iv,
- name: 'AES-GCM',
- };
-
- const wrappedKeyBuffer = await window.crypto.subtle.wrapKey(
- keyExportFormat,
- privateKey,
- wrappingKey,
- wrapAlgorithm,
- );
- const wrappedKey = new Uint8Array(wrappedKeyBuffer);
- const keyAlgorithm = new TextEncoder().encode(JSON.stringify(privateKey.algorithm));
- const payload = concatenate(
- Uint8Array.of(saltLength, ivLength, keyAlgorithm.length),
- salt,
- iv,
- keyAlgorithm,
- wrappedKey);
- chrome.storage.sync.set({ [key]: byteArrayToBase64(payload) }, () => {
- if (!!chrome.runtime.lastError) {
- rej(chrome.runtime.lastError);
- } else {
- res();
- }
- });
- });
-};
diff --git a/src/utils.ts b/src/utils.ts
index 43aedc8..98f6a9c 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,7 +3,6 @@ export function webauthnStringify(o) {
return JSON.stringify(o, (k, v) => {
if (v) {
if (v.constructor.name === 'ArrayBuffer') {
- // Because Buffer.from(ArrayBuffer) was not working on firefox
v = new Uint8Array(v);
}
if (v.constructor.name === 'Uint8Array') {
@@ -16,6 +15,7 @@ export function webauthnStringify(o) {
return v;
});
}
+
export function webauthnParse(j) {
return JSON.parse(j, (k, v) => {
if (v && v.kr_ser_ty === 'Uint8Array') {
@@ -39,6 +39,30 @@ export function concatenate(...arrays: Uint8Array[]) {
return result;
}
+export function counterToBytes(c: number): Uint8Array {
+ const bytes = new Uint8Array(4);
+ // Sadly, JS TypedArrays are whatever-endian the platform is,
+ // so Uint32Array is not at all useful here (or anywhere?),
+ // and we must manually pack the counter (big endian as per spec).
+ bytes[0] = 0xFF & c >>> 24;
+ bytes[1] = 0xFF & c >>> 16;
+ bytes[2] = 0xFF & c >>> 8;
+ bytes[3] = 0xFF & c;
+ return bytes;
+}
+
+/*export function pemToArrayBuffer(pem) {
+ var b64Lines = removeLines(pem);
+ var b64Prefix = b64Lines.replace('-----BEGIN PRIVATE KEY-----', '');
+ var b64Final = b64Prefix.replace('-----END PRIVATE KEY-----', '');
+
+ return base64ToArrayBuffer(b64Final);
+}
+
+function removeLines(str) {
+ return str.replace("\n", "");
+}*/
+
// Copyright 2014 Google Inc. All rights reserved
//
// Use of this source code is governed by a BSD-style
@@ -69,8 +93,8 @@ export function byteArrayToBase64(arr: Uint8Array, urlEncoded: boolean = false):
const result = btoa(String.fromCharCode(...arr));
if (urlEncoded) {
return result.replace(/=/g, '')
- .replace(/\+/g, '-')
- .replace(/\//g, '_');
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_');
}
return result;
}
@@ -85,7 +109,7 @@ export function base64ToByteArray(str: string, urlEncoded: boolean = false): Uin
return Uint8Array.from(atob(rawInput), (c) => c.charCodeAt(0));
}
-function padString(input: string): string {
+export function padString(input: string): string {
let result = input;
while (result.length % 4) {
result += '=';
diff --git a/src/webauth_storage.ts b/src/webauth_storage.ts
new file mode 100644
index 0000000..49118f6
--- /dev/null
+++ b/src/webauth_storage.ts
@@ -0,0 +1,528 @@
+import {
+ base64ToByteArray,
+ byteArrayToBase64,
+ concatenate,
+} from "./utils";
+import {
+ BACKUP_KEY, BD,
+ BD_ENDPOINT,
+ DEFAULT_BD_ENDPOINT, ES256,
+ ivLength,
+ keyExportFormat,
+ RECOVERY_KEY,
+ saltLength,
+ PIN
+} from "./constants";
+import {getLogger} from "./logging";
+import {BackupKey, RecoveryKey} from "./webauthn_psk";
+
+const log = getLogger('auth_storage');
+
+export let SESSION_PIN = null
+
+export class PinStorage {
+ private static saltRounds = 10;
+ public static setSessionPIN(pin: string) {
+ SESSION_PIN = pin;
+ }
+
+ public static resetSessionPIN(): void {
+ this.setSessionPIN(null);
+ }
+
+ public static getSessionPin(): string {
+ if (SESSION_PIN == null) {
+ throw new Error("No session PIN available");
+ }
+ return SESSION_PIN;
+ }
+
+ public static async getPinHash(): Promise {
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get({[PIN]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.getPin', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ }
+
+ if (resp[PIN] == null) {
+ rej('No PIN available. Have you performed the setup for your authenticator?');
+ }
+ log.debug('Loaded PIN hash successfully');
+ res(resp[PIN]);
+ });
+ });
+ };
+
+ public static async setPin(pin: string): Promise {
+ const bcrypt = require('bcryptjs');
+ let hash = bcrypt.hashSync(pin, this.saltRounds);
+
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.set({[PIN]: hash}, () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.setPin', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ log.debug('Set PIN successfully');
+ res();
+ }
+ });
+ });
+ }
+}
+
+export class PSKStorage {
+ public static async getBDEndpoint(): Promise {
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get({[BD_ENDPOINT]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.getBDEndpoint', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ }
+
+ if (resp[BD_ENDPOINT] == null) {
+ log.warn(`No endpoint found, use default endpoint`);
+ res(DEFAULT_BD_ENDPOINT);
+ return;
+ }
+ log.debug('Loaded BD endpoint successfully');
+ res(resp[BD_ENDPOINT]);
+ });
+ });
+ }
+
+ public static async setBDEndpoint(endpoint: string): Promise {
+ log.debug('Set BD endpoint to', endpoint);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.set({[BD_ENDPOINT]: endpoint}, () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.setBDEndpoint', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res();
+ }
+ });
+ });
+ }
+
+ public static async storeBD(bdUUID: string): Promise {
+ log.debug('Store BD');
+ let bds = await this.loadBDs();
+ if (bds.includes(bdUUID)) {
+ return;
+ } else {
+ bds = bds.concat(bdUUID);
+ const exportJSON = JSON.stringify(bds);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.set({[BD]: exportJSON}, () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.storeBD', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res();
+ }
+ });
+ });
+ }
+
+ }
+
+ public static async loadBDs(): Promise> {
+ log.debug(`Loading BDs`);
+ return new Promise>(async (res, rej) => {
+ chrome.storage.local.get({[BD]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.loadBDs', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ }
+
+ if (resp[BD] == null) {
+ log.warn(`No BDs found`);
+ res([]);
+ return;
+ }
+
+ const bds = await JSON.parse(resp[BD]);
+ log.debug('Loaded BDs successfully');
+ res(bds);
+ });
+ });
+ }
+
+ public static async storeBackupKeys(backupKeys: BackupKey[], bdUUID: string, override: boolean = false): Promise {
+ log.debug(`Storing backup keys for`, bdUUID);
+ const backupKeysExists = await this.existBackupKeys(bdUUID);
+ if (backupKeysExists && !override) {
+ log.debug('Backup keys already exist. Update entry.');
+ const entries = await this.loadBackupKeys(bdUUID);
+ backupKeys = entries.concat(backupKeys);
+ }
+
+ let exportJSON = JSON.stringify(backupKeys);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.set({[BACKUP_KEY + '_' + bdUUID]: exportJSON}, () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.storeBackupKeys', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res();
+ }
+ });
+ });
+ };
+
+ public static async loadBackupKeys(bdUUID: string): Promise {
+ log.debug(`Loading backup keys`);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get({[BACKUP_KEY + '_' + bdUUID]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.loadBackupKeys', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ }
+
+ if (resp[BACKUP_KEY + '_' + bdUUID] == null) {
+ log.warn(`No backup keys found`);
+ res([]);
+ return;
+ }
+
+ const backupKeys = await JSON.parse(resp[BACKUP_KEY + '_' + bdUUID]);
+ log.debug('Loaded backup keys successfully');
+ res(backupKeys);
+ });
+ });
+ }
+
+ private static async existBackupKeys(bdUUID: string): Promise {
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get({[BACKUP_KEY + '_' + bdUUID]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.existBackupKeys', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res(!(resp[BACKUP_KEY + '_' + bdUUID] == null));
+ }
+ });
+ });
+ };
+
+ public static async removeRecoveryKey(recoveryKey: RecoveryKey): Promise {
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.remove([RECOVERY_KEY + "_" + recoveryKey.backupKeyId], () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.removeRecoveryKey', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res();
+ }
+ });
+ })
+ }
+
+ public static async storeRecoveryKeys(recoveryKeys: RecoveryKey[]): Promise {
+ log.debug('Storing recovery keys');
+
+ // Export recoveryKeys
+ const exportKeys = new Map();
+ for (let i = 0; i < recoveryKeys.length; i++) {
+ const recKey = recoveryKeys[i];
+ const expPrvKey = await exportKey(recKey.privKey);
+ const expPubKey = await window.crypto.subtle.exportKey('jwk', recKey.pubKey);
+
+ const json = {
+ backupKeyId: recKey.backupKeyId,
+ pubKey: expPubKey,
+ privKey: expPrvKey,
+ delegationSignature: recKey.delegationSignature,
+ bdData: recKey.bdData,
+ }
+
+ exportKeys.set(RECOVERY_KEY + "_" + recKey.backupKeyId, JSON.stringify(json))
+ }
+
+ let storageObject = Object.fromEntries(exportKeys);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.set(storageObject, () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.storeRecoveryKeys', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res();
+ }
+ });
+ });
+ }
+
+ public static async recoveryKeyExists(backupKeyId: string): Promise {
+ log.debug('recoveryKeyExists: Requested backup key ID', backupKeyId);
+ const recoveryKeys = await PSKStorage.loadRecoveryKeys([backupKeyId]);
+ return recoveryKeys.filter(x => x.backupKeyId === backupKeyId).length > 0
+ }
+
+ public static async loadRecoveryKeys(backupKeyIds: string[], privateKeyImport: boolean = true): Promise {
+ log.debug(`Loading recovery keys`);
+ const storageEntries = new Map();
+ for (let i = 0; i < backupKeyIds.length; i++) {
+ storageEntries.set(RECOVERY_KEY + "_" + backupKeyIds[i], null);
+ }
+ let storageObject = Object.fromEntries(storageEntries);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get(storageObject, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform PSKStorage.loadRecoveryKeys', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ }
+
+ const recKeys = new Array();
+ const storageKeys = Array.from(storageEntries.keys());
+ for (let i = 0; i < storageKeys.length; i++) {
+ const storageKey = storageKeys[i];
+ if (resp[storageKey] == null) {
+ continue;
+ }
+
+ const json =await JSON.parse(resp[storageKey]);
+ const prvKey = privateKeyImport ? await importKey(json.privKey) : null;
+ const pubKey = await window.crypto.subtle.importKey(
+ 'jwk',
+ json.pubKey,
+ {
+ name: 'ECDSA',
+ namedCurve: ES256,
+ },
+ true,
+ [],
+ );
+
+ const recKey = new RecoveryKey(json.backupKeyId, pubKey, prvKey, json.delegationSignature, json.bdData);
+ recKeys.push(recKey);
+ }
+ log.debug('Loaded recovery keys successfully', recKeys);
+ res(recKeys);
+ });
+ });
+ }
+}
+
+export class CredentialsMap {
+ public static async put(rpId: string, credSrc: PublicKeyCredentialSource): Promise {
+ log.debug(`Storing credential map entry for ${rpId}`);
+ const mapEntryExists = await this.rpEntryExists(rpId);
+ let credSrcs: PublicKeyCredentialSource[];
+ if (mapEntryExists) {
+ log.debug('Credential map entry does already exist. Update entry.');
+ const entries = await this.load(rpId);
+ entries.push(credSrc);
+ credSrcs = entries;
+ } else {
+ log.debug('Credential map entry does not exist. Create new entry.');
+ credSrcs = new Array(credSrc);
+ }
+
+ // Store PublicKeyCredentialSource as JSON
+ let jsonArr = [];
+ for (let i = 0; i < credSrcs.length; i++) {
+ const json = await credSrcs[i].export();
+ jsonArr.push(json);
+ }
+ let exportJSON = JSON.stringify(jsonArr);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.set({[rpId]: exportJSON}, () => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform CredentialsMap.put', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res();
+ }
+ });
+ });
+ }
+
+
+
+ public static async load(rpId: string, keyImport: boolean = true): Promise {
+ log.debug(`Loading credential map entry for ${rpId}`);
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get({[rpId]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ rej(chrome.runtime.lastError);
+ return;
+ }
+
+ if (resp[rpId] == null) {
+ log.warn(`CredentialsMap entry ${rpId} not found`);
+ res([]);
+ return;
+ }
+
+ const exportJSON = await JSON.parse(resp[rpId]);
+ const credSrcs = new Array();
+ for (let i = 0; i < exportJSON.length; ++i) {
+ const credSrc = await PublicKeyCredentialSource.import(exportJSON[i], keyImport);
+ credSrcs.push(credSrc);
+ }
+ log.debug('Loaded credential map entry successfully');
+ res(credSrcs);
+ });
+ });
+ }
+
+ public static async lookup(rpId: string, credSrcId: string, keyImport: boolean = true): Promise {
+ const credSrcs = await this.load(rpId, keyImport);
+ const res = credSrcs.filter(x => x.id == credSrcId);
+ if (res.length == 0) {
+ return null;
+ } else {
+ return res[0];
+ }
+ };
+
+ public static async rpEntryExists(rpId: string): Promise {
+ return new Promise(async (res, rej) => {
+ chrome.storage.local.get({[rpId]: null}, async (resp) => {
+ if (!!chrome.runtime.lastError) {
+ log.error('Could not perform CredentialsMap.exists', chrome.runtime.lastError.message);
+ rej(chrome.runtime.lastError);
+ return;
+ } else {
+ res(!(resp[rpId] == null));
+ }
+ });
+ });
+ };
+}
+
+export class PublicKeyCredentialSource {
+ public static async import(json: any, keyImport: boolean = true): Promise {
+ const _id = json.id;
+ const _rpId = json.rpId;
+ const _userHandle = json.userHandle;
+ const _privateKey = keyImport? await importKey(json.privateKey) : null;
+
+ return new PublicKeyCredentialSource(_id, _privateKey, _rpId, _userHandle);
+ }
+
+ public id: string
+ public privateKey: CryptoKey
+ public rpId: string
+ public userHandle: Uint8Array
+ public type: string
+
+ constructor(id: string, privateKey: CryptoKey, rpId: string, userHandle?: Uint8Array) {
+ this.id = id;
+ this.privateKey = privateKey;
+ this.rpId = rpId;
+ if (userHandle) {
+ this.userHandle = userHandle;
+ } else {
+ this.userHandle = null;
+ }
+ this.type = "public-key";
+ }
+
+ public async export(): Promise {
+ return {
+ id: this.id,
+ privateKey: await exportKey(this.privateKey),
+ rpId: this.rpId,
+ userHandle: this.userHandle,
+ type: this.type
+ };
+ }
+}
+
+async function exportKey(key: CryptoKey): Promise {
+ const salt = window.crypto.getRandomValues(new Uint8Array(saltLength));
+ const wrappingKey = await getWrappingKey(PinStorage.getSessionPin(), salt);
+ const iv = window.crypto.getRandomValues(new Uint8Array(ivLength));
+ const wrapAlgorithm: AesGcmParams = {
+ iv,
+ name: 'AES-GCM',
+ };
+
+ const wrappedKeyBuffer = await window.crypto.subtle.wrapKey(
+ keyExportFormat,
+ key,
+ wrappingKey,
+ wrapAlgorithm,
+ );
+ const wrappedKey = new Uint8Array(wrappedKeyBuffer);
+ const keyAlgorithm = new TextEncoder().encode(JSON.stringify(key.algorithm));
+ const payload = concatenate(
+ Uint8Array.of(saltLength, ivLength, keyAlgorithm.length),
+ salt,
+ iv,
+ keyAlgorithm,
+ wrappedKey);
+
+ return byteArrayToBase64(payload)
+}
+
+async function importKey(rawKey: string): Promise {
+ const keyPayload = base64ToByteArray(rawKey);
+ const saltByteLength = keyPayload[0];
+ const ivByteLength = keyPayload[1];
+ const keyAlgorithmByteLength = keyPayload[2];
+ let offset = 3;
+ const salt = keyPayload.subarray(offset, offset + saltByteLength);
+ offset += saltByteLength;
+ const iv = keyPayload.subarray(offset, offset + ivByteLength);
+ offset += ivByteLength;
+ const keyAlgorithmBytes = keyPayload.subarray(offset, offset + keyAlgorithmByteLength);
+ offset += keyAlgorithmByteLength;
+ const keyBytes = keyPayload.subarray(offset);
+
+ const wrappingKey = await getWrappingKey(PinStorage.getSessionPin(), salt);
+ const wrapAlgorithm: AesGcmParams = {
+ iv,
+ name: 'AES-GCM',
+ };
+ const unwrappingKeyAlgorithm = JSON.parse(new TextDecoder().decode(keyAlgorithmBytes));
+ return await window.crypto.subtle.unwrapKey(
+ keyExportFormat,
+ keyBytes,
+ wrappingKey,
+ wrapAlgorithm,
+ unwrappingKeyAlgorithm,
+ true,
+ ['sign'],
+ );
+}
+
+const getWrappingKey = async (pin: string, salt: Uint8Array): Promise => {
+ const enc = new TextEncoder();
+ const derivationKey = await window.crypto.subtle.importKey(
+ 'raw',
+ enc.encode(pin),
+ {name: 'PBKDF2', length: 256},
+ false,
+ ['deriveBits', 'deriveKey'],
+ );
+ const pbkdf2Params: Pbkdf2Params = {
+ hash: 'SHA-256',
+ iterations: 100000,
+ name: 'PBKDF2',
+ salt,
+ };
+ return window.crypto.subtle.deriveKey(
+ pbkdf2Params,
+ derivationKey,
+ {name: 'AES-GCM', length: 256},
+ true,
+ ['wrapKey', 'unwrapKey'],
+ );
+};
\ No newline at end of file
diff --git a/src/webauthn.ts b/src/webauthn.ts
deleted file mode 100644
index 9d0dd94..0000000
--- a/src/webauthn.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import * as CBOR from 'cbor';
-import { getCompatibleKey, getCompatibleKeyFromCryptoKey } from './crypto';
-import { getLogger } from './logging';
-import { fetchKey, keyExists, saveKey } from './storage';
-import { base64ToByteArray, byteArrayToBase64, getDomainFromOrigin } from './utils';
-
-const log = getLogger('webauthn');
-
-export const generateRegistrationKeyAndAttestation = async (
- origin: string,
- publicKeyCreationOptions: PublicKeyCredentialCreationOptions,
- pin: string,
-): Promise => {
- if (publicKeyCreationOptions.attestation === 'direct') {
- log.warn('We are being requested to create a key with "direct" attestation');
- log.warn(`We can only perform self-attestation, therefore we will not be provisioning any keys`);
- return null;
- }
- const rp = publicKeyCreationOptions.rp;
- const rpID = rp.id || getDomainFromOrigin(origin);
- const user = publicKeyCreationOptions.user;
- const userID = byteArrayToBase64(new Uint8Array(user.id as ArrayBuffer));
- const keyID = window.btoa(`${userID}@${rpID}`);
-
- // First check if there is already a key for this rp ID
- if (await keyExists(keyID)) {
- throw new Error(`key with id ${keyID} already exists`);
- }
- log.debug('key ID', keyID);
- const compatibleKey = await getCompatibleKey(publicKeyCreationOptions.pubKeyCredParams);
-
- // TODO Increase key counter
- const authenticatorData = await compatibleKey.generateAuthenticatorData(rpID, 0);
- const clientData = await compatibleKey.generateClientData(
- publicKeyCreationOptions.challenge as ArrayBuffer,
- { origin, type: 'webauthn.create' },
- );
- const signature = await compatibleKey.sign(clientData);
-
- const attestationObject = CBOR.encodeCanonical({
- attStmt: {
- alg: compatibleKey.algorithm,
- sig: signature,
- },
- authData: authenticatorData,
- fmt: 'packed',
- }).buffer;
-
- // Now that we have built all we need, let's save the key
- await saveKey(keyID, compatibleKey.privateKey, pin);
-
- return {
- getClientExtensionResults: () => ({}),
- id: keyID,
- rawId: base64ToByteArray(keyID),
- response: {
- attestationObject,
- clientDataJSON: base64ToByteArray(window.btoa(clientData)),
- },
- type: 'public-key',
- } as PublicKeyCredential;
-};
-
-export const generateKeyRequestAndAttestation = async (
- origin: string,
- publicKeyRequestOptions: PublicKeyCredentialRequestOptions,
- pin: string,
-): Promise => {
- if (!publicKeyRequestOptions.allowCredentials) {
- log.debug('No keys requested');
- return null;
- }
- // For now we will only worry about the first entry
- const requestedCredential = publicKeyRequestOptions.allowCredentials[0];
- const keyIDArray: ArrayBuffer = requestedCredential.id as ArrayBuffer;
- const keyID = byteArrayToBase64(new Uint8Array(keyIDArray));
- const key = await fetchKey(keyID, pin);
-
- if (!key) {
- throw new Error(`key with id ${keyID} not found`);
- }
- const compatibleKey = await getCompatibleKeyFromCryptoKey(key);
- const clientData = await compatibleKey.generateClientData(
- publicKeyRequestOptions.challenge as ArrayBuffer,
- {
- origin,
- tokenBinding: {
- status: 'not-supported',
- },
- type: 'webauthn.create',
- },
- );
- const signature = await compatibleKey.sign(clientData);
- const rpID = publicKeyRequestOptions.rpId || getDomainFromOrigin(origin);
- const authenticatorData = await compatibleKey.generateAuthenticatorData(rpID, 0);
- return {
- id: keyID,
- rawId: keyIDArray,
- response: {
- authenticatorData: authenticatorData.buffer,
- clientDataJSON: base64ToByteArray(window.btoa(clientData)),
- signature,
- userHandle: new ArrayBuffer(0), // This should be nullable
- },
- type: 'public-key',
- } as Credential;
-};
diff --git a/src/webauthn_attestation.ts b/src/webauthn_attestation.ts
new file mode 100644
index 0000000..729bc20
--- /dev/null
+++ b/src/webauthn_attestation.ts
@@ -0,0 +1,50 @@
+import {ECDSA, ICOSECompatibleKey, importFromJWK} from "./webauthn_crypto";
+
+const PrivateKeyPEM = '-----BEGIN EC PRIVATE KEY-----\n' +
+ 'MHcCAQEEIOOF5RiIjzKZCCtFJLMxAFB4O8WvnhAsWuF9YacETrWgoAoGCCqGSM49\n' +
+ 'AwEHoUQDQgAEaixHolNWEXlB7JdX+2WgUeM3BfbvLPsVaNKe+Efu4ea3iMBvNelS\n' +
+ '5jOgQ2DYpKv6FHtoUhT6rBpzYmc/pgd6XA==\n' +
+ '-----END EC PRIVATE KEY-----';
+
+const AttestationCertificatePEM = '-----BEGIN CERTIFICATE-----\n' +
+ 'MIIDLzCCAtWgAwIBAgIJAOe4D4tkNjKBMAoGCCqGSM49BAMCMIGwMQswCQYDVQQG\n' +
+ 'EwJERTEPMA0GA1UECAwGU2F4b255MRAwDgYDVQQHDAdEcmVzZGVuMRowGAYDVQQK\n' +
+ 'DBFBdXRoIEV4YW1wbGUsIExMQzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRl\n' +
+ 'c3RhdGlvbjEdMBsGA1UEAwwUQXV0aCBFeGFtcGxlIENvbXBhbnkxHzAdBgkqhkiG\n' +
+ '9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMjAwODIzMDkzODM1WhcNMjEwODIz\n' +
+ 'MDkzODM1WjCBsDELMAkGA1UEBhMCREUxDzANBgNVBAgMBlNheG9ueTEQMA4GA1UE\n' +
+ 'BwwHRHJlc2RlbjEaMBgGA1UECgwRQXV0aCBFeGFtcGxlLCBMTEMxIjAgBgNVBAsM\n' +
+ 'GUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xHTAbBgNVBAMMFEF1dGggRXhhbXBs\n' +
+ 'ZSBDb21wYW55MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMFkwEwYH\n' +
+ 'KoZIzj0CAQYIKoZIzj0DAQcDQgAEaixHolNWEXlB7JdX+2WgUeM3BfbvLPsVaNKe\n' +
+ '+Efu4ea3iMBvNelS5jOgQ2DYpKv6FHtoUhT6rBpzYmc/pgd6XKOB1TCB0jAdBgNV\n' +
+ 'HQ4EFgQU6EhpfP6IdPer5CCYCIsbDOexo2YwHwYDVR0jBBgwFoAU6EhpfP6IdPer\n' +
+ '5CCYCIsbDOexo2YwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwSgYDVR0RBEMwQYIL\n' +
+ 'ZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbYIQbWFpbC5leGFtcGxlLmNvbYIP\n' +
+ 'ZnRwLmV4YW1wbGUuY29tMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRl\n' +
+ 'ZCBDZXJ0aWZpY2F0ZTAKBggqhkjOPQQDAgNIADBFAiEA4Rwn4jcj50HYQ5N6UJaT\n' +
+ 'UxuwhZgl5yLEJOzvY3a2V/gCIFwJNEMUE0PeRrhUoEWmj1zg2kV8EEzHO1bio6q0\n' +
+ 'o9rQ\n' +
+ '-----END CERTIFICATE-----';
+
+export async function createAttestationSignature(hash: Uint8Array, authData: Uint8Array): Promise {
+ const attPrvKey = await getAttestationPrivateKey();
+ const concatData = new Uint8Array(authData.length + hash.length);
+ concatData.set(authData);
+ concatData.set(hash, authData.length);
+ return await attPrvKey.sign(concatData);
+}
+
+async function getAttestationPrivateKey(): Promise {
+ const ECKey = require('ec-key');
+ const prvKey = new ECKey(Buffer.from(PrivateKeyPEM), 'pem');
+ const jwk = JSON.stringify(prvKey, null, 2);
+ const key = await importFromJWK(JSON.parse(jwk), ['sign']);
+ return ECDSA.fromKey(key);
+}
+
+export function getAttestationCertificate(): Uint8Array {
+ const pem = require('pem-file')
+ return pem.decode(Buffer.from(AttestationCertificatePEM));
+}
+
diff --git a/src/webauthn_authenticator.ts b/src/webauthn_authenticator.ts
new file mode 100644
index 0000000..eee3e97
--- /dev/null
+++ b/src/webauthn_authenticator.ts
@@ -0,0 +1,396 @@
+import {ECDSA, ICOSECompatibleKey} from "./webauthn_crypto";
+import {CredentialsMap, PinStorage, PublicKeyCredentialSource} from "./webauth_storage";
+import {base64ToByteArray, byteArrayToBase64, counterToBytes} from "./utils";
+import * as CBOR from 'cbor';
+import {createAttestationSignature, getAttestationCertificate} from "./webauthn_attestation";
+import {getLogger} from "./logging";
+import {ES256_COSE, PSK_EXTENSION_IDENTIFIER} from "./constants";
+import {PSK, RecoveryKey} from "./webauthn_psk";
+
+const log = getLogger('webauthn_authenticator');
+
+export class AssertionResponse {
+ public authenticatorData: Uint8Array
+ public signature: Uint8Array
+ public userHandle: Uint8Array
+ public credentialId: string
+
+ constructor(credId: string, authData: Uint8Array, sign: Uint8Array, userHandle: Uint8Array) {
+ this.authenticatorData = authData;
+ this.signature = sign;
+ this.userHandle = userHandle;
+ this.credentialId = credId;
+ }
+}
+
+export class Authenticator {
+ private static AAGUID: Uint8Array = new Uint8Array([
+ 1214244733, 1205845608, 840015201, 3897052717,
+ 4072880437, 4027233456, 675224361, 2305433287,
+ 74291263, 3461796691, 701523034, 3178201666,
+ 3992003567, 1410532, 4234129691, 1438515639,
+ ]);
+
+ private static getSignatureCounter(): number {
+ return 0;
+ }
+
+ public static async authenticatorGetAssertion(userConsentCallback: () => Promise,
+ rpId: string,
+ hash: Uint8Array,
+ requireUserPresence: boolean,
+ requireUserVerification: boolean,
+ allowCredentialDescriptorList?: PublicKeyCredentialDescriptor[],
+ extensions?: Map
+ ): Promise {
+
+ log.debug('Called authenticatorGetAssertion');
+
+ // Step 2-7 + recovery lookup
+ let recKey: RecoveryKey = null;
+ let credentialOptions: PublicKeyCredentialSource[] = [];
+ if (allowCredentialDescriptorList) {
+ // Simplified credential lookup
+ for (let i = 0; i < allowCredentialDescriptorList.length; i++) {
+ const rawCredId = allowCredentialDescriptorList[i].id as ArrayBuffer;
+ const credId = byteArrayToBase64(new Uint8Array(rawCredId), true);
+ const cred = await CredentialsMap.lookup(rpId, credId, false);
+ if (cred != null) {
+ credentialOptions.push(cred);
+ }
+ }
+ } else {
+ // If no credentials were supplied, load all credentials associated to the RPID
+ credentialOptions = credentialOptions.concat(await CredentialsMap.load(rpId, false));
+ }
+ if (credentialOptions.length == 0) {
+ // Check if there is any recovery key that matches the provided credential descriptors
+ log.debug('No directly managed credentials found');
+ const backupKeyIds = [];
+ for (let i = 0; i < allowCredentialDescriptorList.length; i++) {
+ const rawCredId = allowCredentialDescriptorList[i].id as ArrayBuffer;
+ const credId = byteArrayToBase64(new Uint8Array(rawCredId), true);
+ backupKeyIds.push(credId);
+ }
+ // lookup without private key import, because PIN not available yet
+ recKey = await RecoveryKey.findRecoveryKey(backupKeyIds, false);
+ if (recKey != null) {
+ log.info('Recovery detected for backup key', recKey.backupKeyId);
+ } else {
+ // No recovery and no associated credential found
+ throw new Error(`Container does not manage any related credentials`);
+ }
+ }
+
+ const up = await userConsentCallback();
+ if (!up) {
+ throw new Error(`no user consent`);
+ }
+
+ // User verification is always performed, because PIN is needed to decrypt keys
+ let uv = await this.verifyUser("User verification is required.");
+ if (!uv) {
+ throw new Error(`user verification failed`);
+ }
+
+ // Note: The authenticator won't let the user select a public key credential source
+ let credSource;
+ if (recKey == null) { // No recovery
+ // Load cred source again, now with private key, because UV provided PIN
+ credSource = await CredentialsMap.lookup(rpId, credentialOptions[0].id, true);
+ } else {
+ // Load recovery key again, now with private key, because UV provided PIN
+ recKey = await RecoveryKey.findRecoveryKey([recKey.backupKeyId], true)
+ }
+
+ // Step 8
+ let processedExtensions = undefined;
+ if (extensions) {
+ if (extensions.has(PSK_EXTENSION_IDENTIFIER)) {
+ log.debug('Get: PSK requested');
+ if (recKey == null) {
+ throw new Error('PSK extension requested, but no matching recovery key available');
+ }
+ const rawPskInput = base64ToByteArray(extensions.get(PSK_EXTENSION_IDENTIFIER), true);
+ const pskInput = await CBOR.decode(new Buffer(rawPskInput));
+ if (!pskInput.hasOwnProperty('customClientDataHash')) {
+ throw new Error("PSK extension input has no customClientDataHash");
+ }
+ const customClientDataHash = pskInput['customClientDataHash'];
+ if (!pskInput.hasOwnProperty('userHandle')) {
+ throw new Error("PSK extension input has no userHandle");
+ }
+ const userHandle = new Uint8Array(pskInput['userHandle']).buffer;
+ if (Buffer.byteLength(pskInput.customClientDataHash) != 32) {
+ throw new Error("PSK extension: customClientDataHash has invalid length")
+ }
+ if (userHandle.byteLength == 0) {
+ throw new Error("PSK extension: user handle has invalid length")
+ }
+ const [newCredId, pskOutput] = await PSK.authenticatorGetCredentialExtensionOutput(recKey, customClientDataHash, userHandle, rpId);
+ processedExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, pskOutput]]);
+ credSource = await CredentialsMap.lookup(rpId, newCredId);
+ if (credSource == null) {
+ // This should never happen
+ throw new Error('Get: New credential source missing');
+ }
+ log.debug('Get: Processed PSK');
+ } else if (recKey != null) {
+ throw new Error('Recovery detected, but no PSK requested.')
+ }
+ } else if (recKey != null) {
+ throw new Error('Recovery detected, but no PSK requested.')
+ }
+
+ if (processedExtensions) {
+ processedExtensions = new Uint8Array(CBOR.encodeCanonical(processedExtensions));
+ }
+
+ // Step 9: The current version does not increment the counter
+
+ // Step 10
+ const authenticatorData = await this.generateAuthenticatorData(rpId,
+ this.getSignatureCounter(), undefined, processedExtensions, up, uv);
+
+ // Step 11
+ const concatData = new Uint8Array(authenticatorData.length + hash.length);
+ concatData.set(authenticatorData);
+ concatData.set(hash, authenticatorData.length);
+ const prvKey = await ECDSA.fromKey(credSource.privateKey);
+ const signature = await prvKey.sign(concatData);
+
+ PinStorage.resetSessionPIN();
+
+ // Step 13
+ return new AssertionResponse(credSource.id, authenticatorData, signature, credSource.userHandle);
+ }
+
+ public static async authenticatorMakeCredential(userConsentCallback: () => Promise,
+ hash: Uint8Array,
+ rpEntity: PublicKeyCredentialRpEntity,
+ userEntity: PublicKeyCredentialUserEntity,
+ requireResidentKey: boolean,
+ requireUserPresence: boolean,
+ requireUserVerification: boolean,
+ credTypesAndPubKeyAlgs: PublicKeyCredentialParameters[],
+ excludeCredentialDescriptorList?: PublicKeyCredentialDescriptor[],
+ extensions?: Map): Promise<[string, Uint8Array]> {
+ log.debug('Called authenticatorMakeCredential');
+
+ // Step 2
+ let algCheck = false;
+ for (let i = 0; i < credTypesAndPubKeyAlgs.length; i++) {
+ if (credTypesAndPubKeyAlgs[i].alg == ES256_COSE) {
+ algCheck = true;
+ break;
+ }
+ }
+ if (!algCheck) {
+ throw new Error(`authenticator does not support requested alg`);
+ }
+
+ // Step 3
+ if (excludeCredentialDescriptorList) { // Simplified look up
+ // Load without key import, because PIN is not available yet
+ const credMapEntries = await CredentialsMap.load(rpEntity.id, false);
+ for (let i = 0; i < excludeCredentialDescriptorList.length; i++) {
+ const rawCredId = excludeCredentialDescriptorList[i].id as ArrayBuffer;
+ const credId = byteArrayToBase64(new Uint8Array(rawCredId), true);
+ if (credMapEntries.findIndex(x =>
+ (x.id == credId) && (x.type === excludeCredentialDescriptorList[i].type)) >= 0) {
+ await userConsentCallback();
+ throw new Error(`authenticator manages credential of excludeCredentialDescriptorList`);
+ }
+ }
+ }
+
+ // Step 4 Not needed, because cKey supports resident keys
+
+ // Step 5 + 6
+ const up = await userConsentCallback(); // User presence always checked
+ if (!up) {
+ throw new Error(`no user consent`);
+ }
+
+ // User verification is always performed, because PIN is needed to decrypt keys
+ let uv = await this.verifyUser("The relying party requires user verification.");
+ if (!uv) {
+ throw new Error(`user verification failed`);
+ }
+
+ const credential = await this.finishAuthenticatorMakeCredential(rpEntity.id, hash, uv, up,undefined, extensions, userEntity.id);
+
+ PinStorage.resetSessionPIN();
+
+ return credential;
+ }
+
+ public static async finishAuthenticatorMakeCredential(rpId: string, hash: Uint8Array, uv: boolean, up:boolean, keyPair?: ICOSECompatibleKey, extensions?: Map, userHandle?: BufferSource): Promise<[string, Uint8Array]> {
+ // Step 7
+ if (!(keyPair)) {
+ log.debug('No key pair provided, create new one.');
+ keyPair = await ECDSA.createECDSAKeyPair();
+ }
+ let credentialId = this.createCredentialId();
+ let credentialSource = new PublicKeyCredentialSource(credentialId, keyPair.privateKey, rpId, (userHandle));
+ await CredentialsMap.put(rpId, credentialSource);
+
+ // Step 9
+ let processedExtensions = undefined;
+ if (extensions) {
+ log.debug(extensions);
+ if (extensions.has(PSK_EXTENSION_IDENTIFIER)) {
+ log.debug('Make: PSK requested');
+ const rawPskInput = base64ToByteArray(extensions.get(PSK_EXTENSION_IDENTIFIER), true);
+ const pskInput = await CBOR.decode(new Buffer(rawPskInput));
+ if (pskInput !== true) {
+ log.warn('Make: PSK extension received unexpected input. Skip extension processing.', extensions[PSK_EXTENSION_IDENTIFIER]);
+ } else {
+ const pskOutPut = await PSK.authenticatorMakeCredentialExtensionOutput();
+ processedExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, pskOutPut]]);
+ log.debug('Make: Processed PSK');
+ }
+
+ }
+ }
+ if (processedExtensions) {
+ processedExtensions = new Uint8Array(CBOR.encodeCanonical(processedExtensions));
+ log.debug('CBOR extension', Buffer.from(processedExtensions).toString('hex'));
+ }
+
+
+ // Step 10
+ const sigCnt = this.getSignatureCounter();
+
+ // Step 11
+ const rawCredentialId = base64ToByteArray(credentialId, true);
+ const attestedCredentialData = await this.generateAttestedCredentialData(rawCredentialId, keyPair);
+
+ // Step 12
+ const authenticatorData = await this.generateAuthenticatorData(rpId, sigCnt, attestedCredentialData, processedExtensions, up, uv);
+
+ // Step 13
+ const attObj = await this.generateAttestationObject(hash, authenticatorData);
+
+ log.debug('Created credential', credentialId)
+ return [credentialId, attObj];
+ }
+
+ private static async generateAttestedCredentialData(credentialId: Uint8Array, publicKey: ICOSECompatibleKey): Promise {
+ const aaguid = this.AAGUID.slice(0, 16);
+ const credIdLen = new Uint8Array(2);
+ credIdLen[0] = (credentialId.length >> 8) & 0xff;
+ credIdLen[1] = credentialId.length & 0xff;
+ const coseKey = await publicKey.toCOSE(publicKey.publicKey);
+ const encodedKey = new Uint8Array(CBOR.encodeCanonical(coseKey));
+ log.debug('New pub key', byteArrayToBase64(encodedKey, true));
+
+ const attestedCredentialDataLength = aaguid.length + credIdLen.length + credentialId.length + encodedKey.length;
+ const attestedCredentialData = new Uint8Array(attestedCredentialDataLength);
+
+ let offset = 0;
+ attestedCredentialData.set(aaguid, offset);
+ offset += aaguid.length;
+
+ attestedCredentialData.set(credIdLen, offset);
+ offset += credIdLen.length;
+
+ attestedCredentialData.set(credentialId, offset);
+ offset += credentialId.length;
+
+ attestedCredentialData.set(encodedKey, offset);
+
+ return attestedCredentialData;
+ }
+
+ private static async generateAuthenticatorData(rpID: string, counter: number, attestedCredentialData?: Uint8Array,
+ extensionData?: Uint8Array, up?: boolean, uv?: boolean): Promise {
+ const rpIdDigest = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(rpID));
+ const rpIdHash = new Uint8Array(rpIdDigest);
+ let authenticatorDataLength = rpIdHash.length + 1 + 4;
+ if (attestedCredentialData) {
+ authenticatorDataLength += attestedCredentialData.byteLength;
+ }
+ if (extensionData) {
+ authenticatorDataLength += extensionData.byteLength;
+ }
+
+ const authenticatorData = new Uint8Array(authenticatorDataLength);
+ let offset = 0;
+
+ // 32 bytes for the RP ID hash
+ authenticatorData.set(rpIdHash, offset);
+ offset += rpIdHash.length;
+
+ // 1 byte for flags
+ if (up) {
+ authenticatorData[rpIdHash.length] = 1; // UP
+ }
+ if (uv) {
+ authenticatorData[rpIdHash.length] |= (1 << 2); // AT
+ }
+ if (attestedCredentialData) {
+ authenticatorData[rpIdHash.length] |= (1 << 6); // AT
+ }
+ if (extensionData) {
+ authenticatorData[rpIdHash.length] |= (1 << 7); // ED
+ }
+ offset++;
+
+ // 4 bytes for the counter. big-endian uint32
+ // https://www.w3.org/TR/webauthn/#signature-counter
+ authenticatorData.set(counterToBytes(counter), offset);
+ offset += counterToBytes(counter).length;
+
+ if (attestedCredentialData) {
+ authenticatorData.set(attestedCredentialData, offset);
+ offset += attestedCredentialData.byteLength;
+ }
+ if (extensionData) {
+ authenticatorData.set(extensionData, offset);
+ }
+ return authenticatorData;
+ }
+
+ private static async generateAttestationObject(hash: Uint8Array, authenticatorData: Uint8Array): Promise {
+ const attCert = getAttestationCertificate();
+ const attSignature = await createAttestationSignature(hash, authenticatorData);
+ const attObjJSON = {
+ authData: authenticatorData,
+ fmt: 'packed',
+ attStmt: {
+ alg: ES256_COSE,
+ sig: attSignature,
+ x5c: [attCert]
+ }
+ }
+ return CBOR.encodeCanonical(attObjJSON);
+
+ }
+
+ private static createCredentialId(): string{
+ let enc = new TextEncoder();
+ let dt = new Date().getTime();
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ const r = (dt + Math.random()*16)%16 | 0;
+ dt = Math.floor(dt/16);
+ return (c=='x' ? r :(r&0x3|0x8)).toString(16);
+ });
+ return byteArrayToBase64(enc.encode(uuid), true);
+ }
+
+ public static async verifyUser(message: string): Promise {
+ const bcrypt = require('bcryptjs');
+
+ const userPin = prompt(`${message}\nPlease enter your PIN.`, "");
+ const pinHash = await PinStorage.getPinHash();
+ const match = bcrypt.compareSync(userPin, pinHash);
+
+ if (match) {
+ PinStorage.setSessionPIN(userPin);
+ } else {
+ alert("PIN does not match!");
+ }
+ return match
+ }
+}
\ No newline at end of file
diff --git a/src/webauthn_client.ts b/src/webauthn_client.ts
new file mode 100644
index 0000000..aa38cf8
--- /dev/null
+++ b/src/webauthn_client.ts
@@ -0,0 +1,185 @@
+import * as CBOR from 'cbor';
+import {base64ToByteArray, byteArrayToBase64, getDomainFromOrigin} from "./utils";
+import {Authenticator} from "./webauthn_authenticator";
+import {getLogger} from "./logging";
+import {PSK_EXTENSION_IDENTIFIER} from "./constants";
+
+type FunctionType = string;
+const Create: FunctionType = "webauthn.create";
+const Get: FunctionType = "webauthn.get";
+
+const log = getLogger('webauthn_client');
+
+export async function createPublicKeyCredential(origin: string, options: CredentialCreationOptions, sameOriginWithAncestors: boolean, userConsentCallback: () => Promise): Promise {
+ log.debug('Called createPublicKeyCredential');
+
+ // Step 1
+ if (!options.publicKey) {
+ throw new Error('options missing');
+ }
+
+ if (options.publicKey.attestation === 'none') { // Currently only direct and indirect attestation is supported
+ throw new Error('Client does not support none attestation');
+ }
+
+ // Step 2
+ if (!sameOriginWithAncestors) {
+ throw new Error(`sameOriginWithAncestors has to be true`);
+ }
+
+ // Skip timeout
+
+ // Step 7
+ options.publicKey.rp.id = options.publicKey.rp.id || getDomainFromOrigin(origin);
+
+ // Step 8-10
+ const credTypesAndPubKeyAlgs = options.publicKey.pubKeyCredParams;
+
+ // Step 11 + 12
+ // Only PSK extension is processed
+ let clientExtensions = undefined;
+ let authenticatorExtensions = undefined;
+ if (options.publicKey.extensions) {
+ const reqExt: any = options.publicKey.extensions;
+ if (reqExt.hasOwnProperty(PSK_EXTENSION_IDENTIFIER)) {
+ log.info('PSK extension requested');
+ const authenticatorExtensionInput = new Uint8Array(CBOR.encodeCanonical(true));
+ authenticatorExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, byteArrayToBase64(authenticatorExtensionInput, true)]]);
+ clientExtensions = {[PSK_EXTENSION_IDENTIFIER]: true};
+ }
+ }
+
+ // Step 13 + 14
+ const clientDataJSON = generateClientDataJSON(Create, options.publicKey.challenge as ArrayBuffer, origin);
+
+ // Step 15
+ const clientDataHashDigest = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(clientDataJSON)));
+ const clientDataHash = new Uint8Array(clientDataHashDigest);
+
+ // Handle only 1 authenticator
+ // Step 20, simplified
+ let userVerification = true;
+ let residentKey = false;
+ if (options.publicKey.authenticatorSelection) {
+ if ((options.publicKey.authenticatorSelection.authenticatorAttachment != undefined) && (options.publicKey.authenticatorSelection.authenticatorAttachment !== 'platform')) {
+ throw new Error(`${options.publicKey.authenticatorSelection.authenticatorAttachment} authenticator requested, but only platform authenticators available`);
+ }
+
+ if (options.publicKey.authenticatorSelection.requireResidentKey) {
+ residentKey = options.publicKey.authenticatorSelection.requireResidentKey;
+ }
+
+ if (options.publicKey.authenticatorSelection.userVerification && (options.publicKey.authenticatorSelection.userVerification === 'discouraged')) {
+ userVerification = false;
+ }
+ }
+
+ const userPresence = !userVerification;
+
+ const excludeCredentialDescriptorList = options.publicKey.excludeCredentials // No filtering
+
+ const [credentialId, rawAttObj] = await Authenticator.authenticatorMakeCredential(userConsentCallback,
+ clientDataHash,
+ options.publicKey.rp,
+ options.publicKey.user,
+ residentKey,
+ userPresence,
+ userVerification,
+ credTypesAndPubKeyAlgs,
+ excludeCredentialDescriptorList,
+ authenticatorExtensions);
+
+ log.debug('Received attestation object');
+
+ return {
+ getClientExtensionResults: () => (clientExtensions),
+ id: credentialId,
+ rawId: base64ToByteArray(credentialId, true),
+ response: {
+ attestationObject: rawAttObj.buffer,
+ clientDataJSON: base64ToByteArray(window.btoa(JSON.stringify(clientDataJSON))),
+ },
+ type: 'public-key',
+ } as PublicKeyCredential;
+}
+
+export async function getPublicKeyCredential(origin: string, options: CredentialRequestOptions, sameOriginWithAncestors: boolean, userConsentCallback: () => Promise) {
+ // Step 1
+ if (!options.publicKey) {
+ throw new Error('options missing');
+ }
+
+ // Step 2
+ if (!sameOriginWithAncestors) {
+ throw new Error(`sameOriginWithAncestors has to be true`);
+ }
+
+ // No timeout
+
+ // Step 7
+ const rpID = options.publicKey.rpId || getDomainFromOrigin(origin);
+
+ // Step 8 + 9
+ let clientExtensions = undefined;
+ let authenticatorExtensions = undefined;
+ if (options.publicKey.extensions) {
+ const reqExt: any = options.publicKey.extensions;
+ if (reqExt.hasOwnProperty(PSK_EXTENSION_IDENTIFIER)) {
+ const userHandle = base64ToByteArray(reqExt[PSK_EXTENSION_IDENTIFIER], true);
+ const customClientDataJSON = generateClientDataJSON(Create, options.publicKey.challenge as ArrayBuffer, origin);
+ const customClientDataHashDigest = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(customClientDataJSON)));
+ const customClientDataHash = new Uint8Array(customClientDataHashDigest);
+ const authenticatorExtensionInput = new Uint8Array(CBOR.encodeCanonical({customClientDataHash: customClientDataHash, userHandle: userHandle}));
+ authenticatorExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, byteArrayToBase64(authenticatorExtensionInput, true)]]);
+ clientExtensions = {[PSK_EXTENSION_IDENTIFIER]: true};
+ }
+ }
+
+ // Step 10 + 11
+ const clientDataJSON = generateClientDataJSON(Get, options.publicKey.challenge as ArrayBuffer, origin);
+
+ // Step 12
+ const clientDataHashDigest = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(clientDataJSON)));
+ const clientDataHash = new Uint8Array(clientDataHashDigest);
+
+ // Handle only 1 authenticator
+ // Step 18
+ let userVerification = true;
+ if (options.publicKey.userVerification && (options.publicKey.userVerification === 'discouraged')) {
+ userVerification = false;
+ }
+ const userPresence = !userVerification;
+
+ const allowCredentialDescriptorList = options.publicKey.allowCredentials; // No filtering
+
+ const assertionCreationData = await Authenticator.authenticatorGetAssertion(userConsentCallback,
+ rpID,
+ clientDataHash,
+ userPresence,
+ userVerification,
+ allowCredentialDescriptorList,
+ authenticatorExtensions);
+
+ log.debug('Received assertion response');
+
+ return {
+ getClientExtensionResults: () => (clientExtensions),
+ id: assertionCreationData.credentialId,
+ rawId: base64ToByteArray(assertionCreationData.credentialId, true),
+ response: {
+ authenticatorData: assertionCreationData.authenticatorData.buffer,
+ clientDataJSON: base64ToByteArray(window.btoa(JSON.stringify(clientDataJSON))),
+ signature: assertionCreationData.signature.buffer,
+ userHandle: assertionCreationData.userHandle,
+ },
+ type: 'public-key',
+ } as PublicKeyCredential;
+}
+
+function generateClientDataJSON(type: FunctionType, challenge: ArrayBuffer, origin: string, tokenBinding?: string): any {
+ return {
+ type: type,
+ challenge: byteArrayToBase64(Buffer.from(challenge), true),
+ origin: origin,
+ }
+}
\ No newline at end of file
diff --git a/src/webauthn_crypto.ts b/src/webauthn_crypto.ts
new file mode 100644
index 0000000..29203c9
--- /dev/null
+++ b/src/webauthn_crypto.ts
@@ -0,0 +1,98 @@
+import * as asn1 from 'asn1.js';
+import {BN} from 'bn.js';
+import {base64ToByteArray} from "./utils";
+import {ES256, ES256_COSE, SHA256_COSE} from "./constants";
+
+export interface ICOSECompatibleKey {
+ algorithm: number;
+ privateKey?: CryptoKey;
+ publicKey?: CryptoKey;
+ toCOSE(key: CryptoKey): Promise>;
+ sign(data: Uint8Array): Promise
+}
+
+export class ECDSA implements ICOSECompatibleKey {
+ public algorithm: number
+ public privateKey?: CryptoKey
+ public publicKey?: CryptoKey
+
+ public static async fromKey(key: CryptoKey): Promise {
+ if (key.type === 'public') {
+ return new ECDSA(ES256_COSE, null, key);
+ } else {
+ return new ECDSA(ES256_COSE, key);
+ }
+ }
+
+ public static async createECDSAKeyPair(): Promise {
+ const keyPair = await window.crypto.subtle.generateKey(
+ { name: 'ECDSA', namedCurve: ES256 },
+ true,
+ ['sign'],
+ );
+ return new ECDSA(ES256_COSE, keyPair.privateKey, keyPair.publicKey);
+ }
+
+ constructor(
+ algorithm: number,
+ privateKey: CryptoKey,
+ publicKey?: CryptoKey,
+ ) {
+ this.algorithm = algorithm;
+ this.privateKey = privateKey;
+ if (publicKey) {
+ this.publicKey = publicKey;
+ }
+ }
+
+ public async toCOSE(key: CryptoKey): Promise> {
+ // In JWK the X and Y portions are Base64URL encoded (https://tools.ietf.org/html/rfc7517#section-3),
+ // which is just the right type for COSE encoding (https://tools.ietf.org/html/rfc8152#section-7),
+ // we just need to convert it to a byte array.
+ const exportedKey = await window.crypto.subtle.exportKey('jwk', key);
+ const attData = new Map();
+ attData.set(1, 2); // EC2 key type
+ attData.set(3, this.algorithm);
+ attData.set(-1, SHA256_COSE);
+ attData.set(-2, base64ToByteArray(exportedKey.x, true));
+ attData.set(-3, base64ToByteArray(exportedKey.y, true));
+ return attData;
+ }
+
+ public async sign(data: Uint8Array): Promise {
+ if (!this.privateKey) {
+ throw new Error('no private key available for signing');
+ }
+ const rawSign = await window.crypto.subtle.sign( // Creates digest Hash before signing
+ { name: 'ECDSA', hash: 'SHA-256' },
+ this.privateKey,
+ data,
+ );
+
+ const rawSignBuf = new Buffer(rawSign);
+
+ // Credit to: https://stackoverflow.com/a/39651457/5333936
+ const ecdsaDerSig = asn1.define('ECPrivateKey', function() {
+ return this.seq().obj(
+ this.key('r').int(),
+ this.key('s').int(),
+ );
+ });
+ const r = new BN(rawSignBuf.slice(0, 32).toString('hex'), 16, 'be');
+ const s = new BN(rawSignBuf.slice(32).toString('hex'), 16, 'be');
+ return new Uint8Array(ecdsaDerSig.encode({r, s}, 'der'));
+ }
+}
+
+export async function importFromJWK(jwk, usages): Promise {
+ return window.crypto.subtle.importKey(
+ 'jwk',
+ jwk,
+ {
+ name: 'ECDSA',
+ namedCurve: ES256,
+ },
+ true,
+ usages,
+ );
+}
\ No newline at end of file
diff --git a/src/webauthn_psk.ts b/src/webauthn_psk.ts
new file mode 100644
index 0000000..7ee9a8d
--- /dev/null
+++ b/src/webauthn_psk.ts
@@ -0,0 +1,208 @@
+import * as axios from 'axios';
+import * as CBOR from 'cbor';
+
+import {PinStorage, PSKStorage} from "./webauth_storage";
+import {getLogger} from "./logging";
+import {base64ToByteArray, byteArrayToBase64} from "./utils";
+import {ECDSA} from "./webauthn_crypto";
+import {getAttestationCertificate} from "./webauthn_attestation";
+import {Authenticator} from "./webauthn_authenticator";
+import {BD_TIMEOUT, PSK_EXTENSION_IDENTIFIER} from "./constants";
+
+const log = getLogger('webauthn_psk');
+
+export class BackupKey {
+ public bdAttObj: string; // base64 URL with padding
+
+ constructor(attObj: string) {
+ this.bdAttObj = attObj;
+ }
+
+ static async popBackupKeys(): Promise {
+ const bds = await PSKStorage.loadBDs();
+ const backupKeys = Array();
+
+ for (let i = 0; i < bds.length; i++) {
+ const bdBackupKeys = await PSKStorage.loadBackupKeys(bds[i]);
+ if (bdBackupKeys.length == 0) {
+ throw new Error('No backup keys available for ' + bds[i]);
+ }
+ const backupKey = bdBackupKeys.pop();
+ await PSKStorage.storeBackupKeys(bdBackupKeys, bds[i], true);
+ backupKeys.push(backupKey);
+ }
+ if (backupKeys.length == 0) {
+ throw new Error('No backup keys available');
+ }
+
+ log.debug('Pop backup keys: ', backupKeys)
+
+ return backupKeys;
+ }
+}
+
+export class RecoveryKey {
+ public backupKeyId: string
+ public pubKey: CryptoKey
+ public privKey: CryptoKey
+ public delegationSignature: string
+ public bdData: string
+
+ constructor(backupKeyId: string, pubKey: CryptoKey, privKey: CryptoKey, sign: string, bdData: string) {
+ this.backupKeyId = backupKeyId;
+ this.pubKey = pubKey;
+ this.privKey = privKey;
+ this.delegationSignature = sign;
+ this.bdData = bdData;
+ }
+
+ static async findRecoveryKey(backupKeyIds: string[], importPrvKey: boolean = true): Promise {
+ const recoveryKeys = await PSKStorage.loadRecoveryKeys(backupKeyIds, importPrvKey);
+ if (recoveryKeys.length == 0) {
+ return null
+ }
+
+ return recoveryKeys[0];
+ }
+
+ static async removeRecoveryKey(recKey: RecoveryKey): Promise {
+ return await PSKStorage.removeRecoveryKey(recKey);
+ }
+}
+
+export class PSK {
+ public static async bdDeviceUrl(): Promise {
+ return await PSKStorage.getBDEndpoint();
+ }
+
+ public static async setOptions(alias: string, url: string): Promise {
+ return await PSKStorage.setBDEndpoint(url);
+ }
+
+ public static async pskSetup(): Promise {
+ log.debug('pskSetup triggered');
+
+ const verified = await Authenticator.verifyUser("User verification for PSK setup flow required.");
+ if (!verified) {
+ throw new Error(`user verification failed for PSK setup flow`);
+ }
+
+ const bdEndpoint = await PSKStorage.getBDEndpoint();
+
+ return await axios.default.get(bdEndpoint + '/setup', {timeout: BD_TIMEOUT})
+ .then(async function(response) {
+ log.debug(response);
+ const syncResponse = response.data;
+ const backupKeys = new Array();
+ for (let i = 0; i < syncResponse.backupPublicKeys.length; ++i) {
+ const backupKey = new BackupKey(syncResponse.backupPublicKeys[i].attObj);
+ backupKeys.push(backupKey);
+ }
+ log.debug('Setup finished. Backup keys', backupKeys);
+
+ await PSKStorage.storeBD(syncResponse.bdUUID);
+ await PSKStorage.storeBackupKeys(backupKeys, syncResponse.bdUUID);
+
+ if (syncResponse.hasOwnProperty("recoveryOption")) {
+ await PSK.pskRecoverySetup(syncResponse.authAlias, syncResponse.recoveryOption.originAuthAlias, syncResponse.recoveryOption.keyAmount);
+ }
+ alert('PSK setup flow was successful.')
+ }).catch(e => {
+ alert('PSK Initial Setup Failed!');
+ log.error(e);
+ }).finally(() => PinStorage.resetSessionPIN());
+ }
+
+ private static async pskRecoverySetup(delegatedAuthAlias: string, originAuthAlias: string, keyAmount: number): Promise {
+ log.debug("pskRecoverySetup triggered");
+
+ const bdEndpoint = await PSKStorage.getBDEndpoint();
+
+ let rawRecKeys = new Map()
+ let replacementKeys = []
+ for (let i = 0; i < keyAmount; i++) {
+ const keyPair = await window.crypto.subtle.generateKey(
+ {name: 'ECDSA', namedCurve: 'P-256'},
+ true,
+ ['sign'],
+ );
+ rawRecKeys.set(i.toString(), keyPair);
+
+ // Prepare delegation request
+ const pubKey = await ECDSA.fromKey(keyPair.publicKey);
+ const cosePubKey = await pubKey.toCOSE(pubKey.publicKey);
+ const encodedPubKey = new Uint8Array(CBOR.encodeCanonical(cosePubKey));
+ replacementKeys.push({replacementKeyId: i.toString(), pubKey: byteArrayToBase64(encodedPubKey, true)});
+ }
+
+ let attCert = byteArrayToBase64(getAttestationCertificate(), true);
+
+ return await axios.default.post(bdEndpoint + '/setup', {
+ replacementKeys,
+ attCert,
+ delegatedAuthAlias,
+ originAuthAlias
+ }, {timeout: BD_TIMEOUT})
+ .then(async function (delResponse) {
+ const rawDelegations = delResponse.data.delegations;
+
+ let recoveryKeys = new Array()
+
+ for (let i = 0; i < rawDelegations.length; ++i) {
+ const sign = rawDelegations[i].sign;
+ const backupKeyId = rawDelegations[i].backupKeyId;
+ const replacementKeyId = rawDelegations[i].replacementKeyId;
+ const bdData = rawDelegations[i].bdData;
+
+ const keyPair = rawRecKeys.get(replacementKeyId)
+ if (!keyPair) {
+ log.warn('BD response does not contain delegation for key pair', replacementKeyId);
+ continue;
+ }
+
+ const pubKey = keyPair.publicKey;
+ const privKey = keyPair.privateKey;
+
+ const recoveryKey = new RecoveryKey(backupKeyId, pubKey, privKey, sign, bdData)
+
+ recoveryKeys.push(recoveryKey);
+ }
+
+ log.debug('Recovery Setup finished. Recovery keys:', recoveryKeys);
+ await PSKStorage.storeRecoveryKeys(recoveryKeys);
+ }).catch(e => {
+ alert('PSK Recovery Setup Failed!');
+ log.error(e);
+ });
+ }
+
+ public static async authenticatorMakeCredentialExtensionOutput(): Promise {
+ const backupKeys = await BackupKey.popBackupKeys();
+ const raw_backup_keys = Array();
+ for (let i = 0; i < backupKeys.length; i++) {
+ raw_backup_keys.push(base64ToByteArray(backupKeys[i].bdAttObj, true));
+ }
+ return raw_backup_keys;
+ }
+
+ public static async authenticatorGetCredentialExtensionOutput(recoveryKey: RecoveryKey, customClientDataHash: Uint8Array, userHandle: ArrayBuffer, rpId: string): Promise<[string, any]> {
+ log.debug('authenticatorGetCredentialExtensionOutput called');
+
+ // Create attestation object using the key pair of the recovery key + request PSK extension
+ const keyPair = await ECDSA.fromKey(recoveryKey.privKey);
+ keyPair.publicKey = recoveryKey.pubKey;
+ const authenticatorExtensionInput = new Uint8Array(CBOR.encodeCanonical(true));
+ const authenticatorExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, byteArrayToBase64(authenticatorExtensionInput, true)]]);
+ const [credentialId, rawAttObj] = await Authenticator.finishAuthenticatorMakeCredential(rpId, customClientDataHash, true, true, keyPair, authenticatorExtensions, userHandle);
+
+ log.debug('Delegation signature', recoveryKey.delegationSignature);
+ log.debug('Attestation object', byteArrayToBase64(rawAttObj, true));
+ log.debug('BDData', recoveryKey.bdData);
+
+ // Finally remove recovery key since PSK output was generated successfully
+ await RecoveryKey.removeRecoveryKey(recoveryKey);
+
+ const recoveryMessage = {attestationObject: rawAttObj, oldBackupKeyId: base64ToByteArray(recoveryKey.backupKeyId, true), delegationSignature: base64ToByteArray(recoveryKey.delegationSignature, true), bdData: base64ToByteArray(recoveryKey.bdData, true)}
+ return [credentialId, recoveryMessage]
+ }
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 33775b3..fc89866 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es6",
+ "target": "esnext",
"module": "commonjs",
"sourceMap": true,
"esModuleInterop": true,
@@ -11,7 +11,7 @@
"typeRoots": [
"node_modules/@types",
"node_modules/web-ext-types",
- "types",
+ "types"
]
}
}
\ No newline at end of file
diff --git a/webpack.common.js b/webpack.common.js
index d023b14..ea78baf 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -8,7 +8,8 @@ module.exports = {
'chromium/js/background.js': './src/background.ts',
'chromium/js/content_script.js': './src/content_script.ts',
'chromium/js/inject_webauthn.js': './src/inject_webauthn.ts',
- 'chromium/js/popup.js': './src/popup.ts'
+ 'chromium/js/popup.js': './src/popup.ts',
+ 'chromium/js/options.js': './src/options.ts'
},
output: {
path: path.resolve(__dirname, 'dist'),