diff --git a/oauth3.crypto.js b/oauth3.crypto.js index d87a158..bb83280 100644 --- a/oauth3.crypto.js +++ b/oauth3.crypto.js @@ -8,6 +8,9 @@ OAUTH3.crypto.core = require('./oauth3.node.crypto'); } catch (error) { OAUTH3.crypto.core = {}; + OAUTH3.crypto.core.ready = false; + var finishBeforeReady = []; + var deferedCalls = []; // We don't currently have a fallback method for this function, so we assign // it directly to the core object instead of the webCrypto object. @@ -17,10 +20,31 @@ }; var webCrypto = {}; + + var deferCryptoCall = function(name) { + return function() { + var args = arguments; + return new OAUTH3.PromiseA(function(resolve, reject) { + deferedCalls.push(function(){ + try { + webCrypto[name].apply(webCrypto, args) + .then(function(result){ + resolve(result); + }); + } catch(e) { + reject(e); + } + }); + }); + }; + }; + + OAUTH3.crypto.core.sha256 = deferCryptoCall("sha256"); webCrypto.sha256 = function (buf) { return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf); }; + OAUTH3.crypto.core.pbkdf2 = deferCryptoCall("pbkdf2"); webCrypto.pbkdf2 = function (password, salt) { return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey']) .then(function (key) { @@ -32,12 +56,15 @@ }); }; + OAUTH3.crypto.core.encrypt = deferCryptoCall("encrypt"); webCrypto.encrypt = function (rawKey, iv, data) { return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt']) .then(function (key) { return OAUTH3._browser.window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data); }); }; + + OAUTH3.crypto.core.decrypt = deferCryptoCall("decrypt"); webCrypto.decrypt = function (rawKey, iv, data) { return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt']) .then(function (key) { @@ -45,6 +72,7 @@ }); }; + OAUTH3.crypto.core.genEcdsaKeyPair = deferCryptoCall("genEcdsaKeyPair"); webCrypto.genEcdsaKeyPair = function () { return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) .then(function (keyPair) { @@ -57,6 +85,7 @@ }); }; + OAUTH3.crypto.core.sign = deferCryptoCall("sign"); webCrypto.sign = function (jwk, msg) { return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) .then(function (key) { @@ -66,6 +95,8 @@ return new Uint8Array(sig); }); }; + + OAUTH3.crypto.core.verify = deferCryptoCall("verify"); webCrypto.verify = function (jwk, msg, signature) { // If the JWK has properties that should only exist on the private key or is missing // "verify" in the key_ops, importing in as a public key won't work. @@ -103,18 +134,19 @@ return prom; }; function checkException(name, func) { - OAUTH3.PromiseA.resolve().then(func) + return OAUTH3.PromiseA.resolve().then(func) .then(function () { OAUTH3.crypto.core[name] = webCrypto[name]; }, function (err) { console.warn('error with WebCrypto', name, '- using fallback', err); - loadFallback().then(function () { + return loadFallback().then(function () { OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; }); }); } function checkResult(name, expected, func) { - checkException(name, function () { + + finishBeforeReady.push(checkException(name, function () { return func() .then(function (result) { if (typeof expected === typeof result) { @@ -127,7 +159,7 @@ throw new Error("result ("+result+") doesn't match expectation ("+expected+")"); } }); - }); + })); } var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); @@ -159,12 +191,20 @@ return webCrypto.verify(jwk, dataBuf, sig); }); // The results of these functions are less predictable, so we can't check their return value. - checkException('genEcdsaKeyPair', function () { + finishBeforeReady.push(checkException('genEcdsaKeyPair', function () { return webCrypto.genEcdsaKeyPair(); - }); - checkException('sign', function () { + })); + finishBeforeReady.push(checkException('sign', function () { return webCrypto.sign(jwk, dataBuf); - }); + })); + + OAUTH3.PromiseA.all(finishBeforeReady) + .then(function(results) { + OAUTH3.crypto.core.ready = true; + deferedCalls.forEach(function(request) { + request(); + }); + }); } checkWebCrypto(); }