|
|
@ -3,160 +3,171 @@ |
|
|
|
|
|
|
|
var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; |
|
|
|
|
|
|
|
var loadFallback = function() { |
|
|
|
var prom; |
|
|
|
loadFallback = function () { return prom; }; |
|
|
|
OAUTH3.crypto = {}; |
|
|
|
try { |
|
|
|
OAUTH3.crypto.core = require('./oauth3.node.crypto'); |
|
|
|
} catch (error) { |
|
|
|
OAUTH3.crypto.core = {}; |
|
|
|
|
|
|
|
prom = new OAUTH3.PromiseA(function (resolve) { |
|
|
|
var body = document.getElementsByTagName('body')[0]; |
|
|
|
var script = document.createElement('script'); |
|
|
|
script.type = 'text/javascript'; |
|
|
|
script.onload = resolve; |
|
|
|
script.onreadystatechange = function () { |
|
|
|
if (this.readyState === 'complete' || this.readyState === 'loaded') { |
|
|
|
resolve(); |
|
|
|
} |
|
|
|
}; |
|
|
|
script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js'; |
|
|
|
body.appendChild(script); |
|
|
|
}); |
|
|
|
return prom; |
|
|
|
}; |
|
|
|
// 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.
|
|
|
|
OAUTH3.crypto.core.randomBytes = function (size) { |
|
|
|
var buf = OAUTH3._browser.window.crypto.getRandomValues(new Uint8Array(size)); |
|
|
|
return OAUTH3.PromiseA.resolve(buf); |
|
|
|
}; |
|
|
|
|
|
|
|
var webCrypto = {}; |
|
|
|
webCrypto.sha256 = function (buf) { |
|
|
|
return crypto.subtle.digest({name: 'SHA-256'}, buf); |
|
|
|
}; |
|
|
|
var webCrypto = {}; |
|
|
|
webCrypto.sha256 = function (buf) { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf); |
|
|
|
}; |
|
|
|
|
|
|
|
webCrypto.pbkdf2 = function (password, salt) { |
|
|
|
return crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey']) |
|
|
|
.then(function (key) { |
|
|
|
var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; |
|
|
|
return crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, true, ['encrypt', 'decrypt']); |
|
|
|
}) |
|
|
|
.then(function (key) { |
|
|
|
return crypto.subtle.exportKey('raw', key); |
|
|
|
}); |
|
|
|
}; |
|
|
|
webCrypto.pbkdf2 = function (password, salt) { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(password), {name: 'PBKDF2'}, false, ['deriveKey']) |
|
|
|
.then(function (key) { |
|
|
|
var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; |
|
|
|
return OAUTH3._browser.window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, true, ['encrypt', 'decrypt']); |
|
|
|
}) |
|
|
|
.then(function (key) { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.exportKey('raw', key); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
webCrypto.encrypt = function (rawKey, iv, data) { |
|
|
|
return crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['encrypt']) |
|
|
|
.then(function (key) { |
|
|
|
return crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data); |
|
|
|
}); |
|
|
|
}; |
|
|
|
webCrypto.decrypt = function (rawKey, iv, data) { |
|
|
|
return crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt']) |
|
|
|
.then(function (key) { |
|
|
|
return crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, data); |
|
|
|
}); |
|
|
|
}; |
|
|
|
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); |
|
|
|
}); |
|
|
|
}; |
|
|
|
webCrypto.decrypt = function (rawKey, iv, data) { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.importKey('raw', rawKey, {name: 'AES-GCM'}, false, ['decrypt']) |
|
|
|
.then(function (key) { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, data); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
webCrypto.genEcdsaKeyPair = function () { |
|
|
|
return crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) |
|
|
|
.then(function (keyPair) { |
|
|
|
return OAUTH3.PromiseA.all([ |
|
|
|
crypto.subtle.exportKey('jwk', keyPair.privateKey) |
|
|
|
, crypto.subtle.exportKey('jwk', keyPair.publicKey) |
|
|
|
]); |
|
|
|
}).then(function (jwkPair) { |
|
|
|
return { privateKey: jwkPair[0], publicKey: jwkPair[1] }; |
|
|
|
}); |
|
|
|
}; |
|
|
|
webCrypto.genEcdsaKeyPair = function () { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) |
|
|
|
.then(function (keyPair) { |
|
|
|
return OAUTH3.PromiseA.all([ |
|
|
|
OAUTH3._browser.window.crypto.subtle.exportKey('jwk', keyPair.privateKey) |
|
|
|
, OAUTH3._browser.window.crypto.subtle.exportKey('jwk', keyPair.publicKey) |
|
|
|
]); |
|
|
|
}).then(function (jwkPair) { |
|
|
|
return { privateKey: jwkPair[0], publicKey: jwkPair[1] }; |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
webCrypto.sign = function (jwk, msg) { |
|
|
|
return crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['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) { |
|
|
|
return OAUTH3._browser.window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, msg); |
|
|
|
}) |
|
|
|
.then(function (sig) { |
|
|
|
return new Uint8Array(sig); |
|
|
|
}); |
|
|
|
}; |
|
|
|
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.
|
|
|
|
if (jwk.hasOwnProperty('d') || jwk.hasOwnProperty('key_ops')) { |
|
|
|
jwk = JSON.parse(JSON.stringify(jwk)); |
|
|
|
delete jwk.d; |
|
|
|
delete jwk.key_ops; |
|
|
|
} |
|
|
|
|
|
|
|
return OAUTH3._browser.window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['verify']) |
|
|
|
.then(function (key) { |
|
|
|
return crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, msg); |
|
|
|
}) |
|
|
|
.then(function (sig) { |
|
|
|
return new Uint8Array(sig); |
|
|
|
return OAUTH3._browser.window.crypto.subtle.verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, signature, msg); |
|
|
|
}); |
|
|
|
}; |
|
|
|
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.
|
|
|
|
if (jwk.hasOwnProperty('d') || jwk.hasOwnProperty('key_ops')) { |
|
|
|
jwk = JSON.parse(JSON.stringify(jwk)); |
|
|
|
delete jwk.d; |
|
|
|
delete jwk.key_ops; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
return crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['verify']) |
|
|
|
.then(function (key) { |
|
|
|
return crypto.subtle.verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, signature, msg); |
|
|
|
}); |
|
|
|
}; |
|
|
|
function checkWebCrypto() { |
|
|
|
var loadFallback = function() { |
|
|
|
var prom; |
|
|
|
loadFallback = function () { return prom; }; |
|
|
|
|
|
|
|
OAUTH3.crypto = {}; |
|
|
|
OAUTH3.crypto.core = {}; |
|
|
|
function checkWebCrypto() { |
|
|
|
function checkException(name, func) { |
|
|
|
new OAUTH3.PromiseA(function (resolve) { resolve(func()); }) |
|
|
|
.then(function () { |
|
|
|
OAUTH3.crypto.core[name] = webCrypto[name]; |
|
|
|
}) |
|
|
|
.catch(function (err) { |
|
|
|
console.warn('error with WebCrypto', name, '- using fallback', err); |
|
|
|
loadFallback().then(function () { |
|
|
|
OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
function checkResult(name, expected, func) { |
|
|
|
checkException(name, function () { |
|
|
|
return func() |
|
|
|
.then(function (result) { |
|
|
|
if (typeof expected === typeof result) { |
|
|
|
return result; |
|
|
|
prom = new OAUTH3.PromiseA(function (resolve) { |
|
|
|
var body = document.getElementsByTagName('body')[0]; |
|
|
|
var script = document.createElement('script'); |
|
|
|
script.type = 'text/javascript'; |
|
|
|
script.onload = resolve; |
|
|
|
script.onreadystatechange = function () { |
|
|
|
if (this.readyState === 'complete' || this.readyState === 'loaded') { |
|
|
|
resolve(); |
|
|
|
} |
|
|
|
return OAUTH3._base64.bufferToUrlSafe(result); |
|
|
|
}; |
|
|
|
script.src = '/assets/org.oauth3/oauth3.crypto.fallback.js'; |
|
|
|
body.appendChild(script); |
|
|
|
}); |
|
|
|
return prom; |
|
|
|
}; |
|
|
|
function checkException(name, func) { |
|
|
|
new OAUTH3.PromiseA(function (resolve) { resolve(func()); }) |
|
|
|
.then(function () { |
|
|
|
OAUTH3.crypto.core[name] = webCrypto[name]; |
|
|
|
}) |
|
|
|
.then(function (result) { |
|
|
|
if (result !== expected) { |
|
|
|
throw new Error("result ("+result+") doesn't match expectation ("+expected+")"); |
|
|
|
} |
|
|
|
.catch(function (err) { |
|
|
|
console.warn('error with WebCrypto', name, '- using fallback', err); |
|
|
|
loadFallback().then(function () { |
|
|
|
OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
function checkResult(name, expected, func) { |
|
|
|
checkException(name, function () { |
|
|
|
return func() |
|
|
|
.then(function (result) { |
|
|
|
if (typeof expected === typeof result) { |
|
|
|
return result; |
|
|
|
} |
|
|
|
return OAUTH3._base64.bufferToUrlSafe(result); |
|
|
|
}) |
|
|
|
.then(function (result) { |
|
|
|
if (result !== expected) { |
|
|
|
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]); |
|
|
|
var dataBuf = OAUTH3._base64.urlSafeToBuffer('1234567890abcdefghijklmn'); |
|
|
|
var keyBuf = OAUTH3._base64.urlSafeToBuffer('l_Aeoqk6ePjwjCYrlHrgrg'); |
|
|
|
var encBuf = OAUTH3._base64.urlSafeToBuffer('Ji_gEtcNElUONSR4Mf9S75davXjh_6-oQN9AgO5UF8rERw'); |
|
|
|
checkResult('sha256', 'BwMveUm2V1axuERvUoxM4dScgNl9yKhER9a6p80GXj4', function () { |
|
|
|
return webCrypto.sha256(dataBuf); |
|
|
|
}); |
|
|
|
checkResult('pbkdf2', OAUTH3._base64.bufferToUrlSafe(keyBuf), function () { |
|
|
|
return webCrypto.pbkdf2('password', zeroBuf); |
|
|
|
}); |
|
|
|
checkResult('encrypt', OAUTH3._base64.bufferToUrlSafe(encBuf), function () { |
|
|
|
return webCrypto.encrypt(keyBuf, zeroBuf.slice(0, 12), dataBuf); |
|
|
|
}); |
|
|
|
checkResult('decrypt', OAUTH3._base64.bufferToUrlSafe(dataBuf), function () { |
|
|
|
return webCrypto.decrypt(keyBuf, zeroBuf.slice(0, 12), encBuf); |
|
|
|
}); |
|
|
|
var zeroBuf = new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); |
|
|
|
var dataBuf = OAUTH3._base64.urlSafeToBuffer('1234567890abcdefghijklmn'); |
|
|
|
var keyBuf = OAUTH3._base64.urlSafeToBuffer('l_Aeoqk6ePjwjCYrlHrgrg'); |
|
|
|
var encBuf = OAUTH3._base64.urlSafeToBuffer('Ji_gEtcNElUONSR4Mf9S75davXjh_6-oQN9AgO5UF8rERw'); |
|
|
|
checkResult('sha256', 'BwMveUm2V1axuERvUoxM4dScgNl9yKhER9a6p80GXj4', function () { |
|
|
|
return webCrypto.sha256(dataBuf); |
|
|
|
}); |
|
|
|
checkResult('pbkdf2', OAUTH3._base64.bufferToUrlSafe(keyBuf), function () { |
|
|
|
return webCrypto.pbkdf2('password', zeroBuf); |
|
|
|
}); |
|
|
|
checkResult('encrypt', OAUTH3._base64.bufferToUrlSafe(encBuf), function () { |
|
|
|
return webCrypto.encrypt(keyBuf, zeroBuf.slice(0, 12), dataBuf); |
|
|
|
}); |
|
|
|
checkResult('decrypt', OAUTH3._base64.bufferToUrlSafe(dataBuf), function () { |
|
|
|
return webCrypto.decrypt(keyBuf, zeroBuf.slice(0, 12), encBuf); |
|
|
|
}); |
|
|
|
|
|
|
|
var jwk = { |
|
|
|
kty: "EC" |
|
|
|
, crv: "P-256" |
|
|
|
, d: "ChXx7ea5YtEltCufA8CVb0lQv3glcCfcSpEgdedgIP0" |
|
|
|
, x: "Akt5ZDbytcKS5UQMURvGb_UIMS4qFctDwrX8bX22ato" |
|
|
|
, y: "cV7nhpWNT1FeRIbdold4jLtgsEpZBFcNy3p2E5mqvto" |
|
|
|
}; |
|
|
|
var sig = OAUTH3._base64.urlSafeToBuffer('nc3F8qeP8OXpfqPD9tTcFQg0Wfp37RTAppLPIKE1ZupR_8Aba64hNExwd1dOk802OFQxaECPDZCkKe7WA9RXAg'); |
|
|
|
checkResult('verify', true, function() { |
|
|
|
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 () { |
|
|
|
return webCrypto.genEcdsaKeyPair(); |
|
|
|
}); |
|
|
|
checkException('sign', function () { |
|
|
|
return webCrypto.sign(jwk, dataBuf); |
|
|
|
}); |
|
|
|
var jwk = { |
|
|
|
kty: "EC" |
|
|
|
, crv: "P-256" |
|
|
|
, d: "ChXx7ea5YtEltCufA8CVb0lQv3glcCfcSpEgdedgIP0" |
|
|
|
, x: "Akt5ZDbytcKS5UQMURvGb_UIMS4qFctDwrX8bX22ato" |
|
|
|
, y: "cV7nhpWNT1FeRIbdold4jLtgsEpZBFcNy3p2E5mqvto" |
|
|
|
}; |
|
|
|
var sig = OAUTH3._base64.urlSafeToBuffer('nc3F8qeP8OXpfqPD9tTcFQg0Wfp37RTAppLPIKE1ZupR_8Aba64hNExwd1dOk802OFQxaECPDZCkKe7WA9RXAg'); |
|
|
|
checkResult('verify', true, function() { |
|
|
|
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 () { |
|
|
|
return webCrypto.genEcdsaKeyPair(); |
|
|
|
}); |
|
|
|
checkException('sign', function () { |
|
|
|
return webCrypto.sign(jwk, dataBuf); |
|
|
|
}); |
|
|
|
} |
|
|
|
checkWebCrypto(); |
|
|
|
} |
|
|
|
checkWebCrypto(); |
|
|
|
|
|
|
|
OAUTH3.crypto.thumbprintJwk = function (jwk) { |
|
|
|
var keys; |
|
|
@ -183,12 +194,12 @@ |
|
|
|
}; |
|
|
|
|
|
|
|
OAUTH3.crypto._createKey = function (ppid) { |
|
|
|
var kekPromise, ecdsaPromise; |
|
|
|
var salt = window.crypto.getRandomValues(new Uint8Array(16)); |
|
|
|
|
|
|
|
kekPromise = OAUTH3.crypto.core.pbkdf2(ppid, salt); |
|
|
|
var saltProm = OAUTH3.crypto.core.randomBytes(16); |
|
|
|
var kekProm = saltProm.then(function (salt) { |
|
|
|
return OAUTH3.crypto.core.pbkdf2(ppid, salt); |
|
|
|
}); |
|
|
|
|
|
|
|
ecdsaPromise = OAUTH3.crypto.core.genEcdsaKeyPair() |
|
|
|
var ecdsaProm = OAUTH3.crypto.core.genEcdsaKeyPair() |
|
|
|
.then(function (keyPair) { |
|
|
|
return OAUTH3.crypto.thumbprintJwk(keyPair.publicKey).then(function (kid) { |
|
|
|
keyPair.privateKey.alg = keyPair.publicKey.alg = 'ES256'; |
|
|
@ -197,18 +208,28 @@ |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise]).then(function (keys) { |
|
|
|
var ecdsaIv = window.crypto.getRandomValues(new Uint8Array(12)); |
|
|
|
var secretIv = window.crypto.getRandomValues(new Uint8Array(12)); |
|
|
|
var userSecret = window.crypto.getRandomValues(new Uint8Array(16)); |
|
|
|
return OAUTH3.PromiseA.all([ |
|
|
|
kekProm |
|
|
|
, ecdsaProm |
|
|
|
, saltProm |
|
|
|
, OAUTH3.crypto.core.randomBytes(16) |
|
|
|
, OAUTH3.crypto.core.randomBytes(12) |
|
|
|
, OAUTH3.crypto.core.randomBytes(12) |
|
|
|
]).then(function (results) { |
|
|
|
var kek = results[0]; |
|
|
|
var keyPair = results[1]; |
|
|
|
var salt = results[2]; |
|
|
|
var userSecret = results[3]; |
|
|
|
var ecdsaIv = results[4]; |
|
|
|
var secretIv = results[5]; |
|
|
|
|
|
|
|
return OAUTH3.PromiseA.all([ |
|
|
|
OAUTH3.crypto.core.encrypt(keys[0], ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[1].privateKey))) |
|
|
|
, OAUTH3.crypto.core.encrypt(keys[0], secretIv, userSecret) |
|
|
|
OAUTH3.crypto.core.encrypt(kek, ecdsaIv, OAUTH3._binStr.binStrToBuffer(JSON.stringify(keyPair.privateKey))) |
|
|
|
, OAUTH3.crypto.core.encrypt(kek, secretIv, userSecret) |
|
|
|
]) |
|
|
|
.then(function (encrypted) { |
|
|
|
return { |
|
|
|
publicKey: keys[1].publicKey |
|
|
|
publicKey: keyPair.publicKey |
|
|
|
, privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) |
|
|
|
, userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) |
|
|
|
, salt: OAUTH3._base64.bufferToUrlSafe(salt) |
|
|
|