Reputation: 1151
The following code works in Firefox 76.0.1:
"use strict"
let RSAKeys
(async () => {
RSAKeys = await crypto.subtle.generateKey({
name: "RSA-OAEP",
modulusLength: 3072,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"},
true,
["wrapKey", "unwrapKey"])
alert(JSON.stringify(Object.fromEntries(
await Promise.all(Object.entries(RSAKeys).map(async ([k, v], i) =>
[k, await cryptoBase64("exportKey", ["pkcs8", "spki"][i], v)])))))
})()
async function cryptoBase64(primitive, ...args) {
return ArrayBufferToBase64(await crypto.subtle[primitive](...args))
}
function ArrayBufferToBase64(buf) {
return btoa([...new Uint8Array(buf)].map(x => String.fromCharCode(x)).join(""))
}
but in Chromium 80 I get:
Uncaught (in promise) DOMException: The key is not of the expected type
Whence the difference? Is it a bug in Chromium? And is there a workaround?
(Related to this question. After applying the solution I still had a problem and it turns out there's another discrepancy between browsers I'm running into.)
Upvotes: 1
Views: 616
Reputation: 49251
Object.entries
returns an array with the properties of the object as key-value pairs. The order of the key-value pairs is arbitrary, see Object.entries()
:
The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs, in the same order as that provided by a for...in loop...
and for...in:
A for...in loop iterates over the properties of an object in an arbitrary order...
On the other hand, ["pkcs8", "spki"][i]
assumes that the key order is private key (i = 0) followed by the public key (i = 1). In the Firfox browser the order matches coincidentally, in the Chromium browser not, which causes the exception.
The problem can be solved by sorting the array, e.g. using sort()
and localeCompare()
, see also the recommendation in Object.entries()
:
sort((key1, key2) => key1[0].localeCompare(key2[0]))
Another approach would be to set the format (pkcs8
, spki
) depending on the key type (private, public) instead of sorting.
Your JavaScript code completed with one of the two approaches runs in both, the Firefox and the Chromium browsers:
"use strict"
let RSAKeys
(async () => {
RSAKeys = await crypto.subtle.generateKey({
name: "RSA-OAEP",
modulusLength: 3072,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"},
true,
["wrapKey", "unwrapKey"])
// Approach 1
var result1 = JSON.stringify(Object.fromEntries(
await Promise.all(Object.entries(RSAKeys)
.sort((key1, key2) => key1[0].localeCompare(key2[0]))
.map(async ([k, v], i) => [k, await cryptoBase64("exportKey", ["pkcs8", "spki"][i], v)]))))
console.log(result1.replace(/(.{64})/g, "$1\n"));
// Approach 2
var result2 = JSON.stringify(Object.fromEntries(
await Promise.all(Object.entries(RSAKeys)
.map(async ([k, v], i) => [k, await cryptoBase64("exportKey", k == "privateKey" ? "pkcs8" : "spki", v)]))))
console.log(result2.replace(/(.{64})/g, "$1\n"));
})()
async function cryptoBase64(primitive, ...args) {
return ArrayBufferToBase64(await crypto.subtle[primitive](...args))
}
function ArrayBufferToBase64(buf) {
return btoa([...new Uint8Array(buf)].map(x => String.fromCharCode(x)).join(""))
}
Upvotes: 1