implemented dynamic loading of fallback crypto functions
This commit is contained in:
		
							parent
							
								
									740c973afc
								
							
						
					
					
						commit
						01580dd6b3
					
				| @ -10,23 +10,25 @@ | |||||||
|     return createHash('sha256').update(buf).digest(); |     return createHash('sha256').update(buf).digest(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function encrypt(data, password, salt, iv) { |   function runPbkdf2(password, salt) { | ||||||
|     // Derived AES key is 128 bit, and the function takes a size in bytes.
 |     // Derived AES key is 128 bit, and the function takes a size in bytes.
 | ||||||
|     var aesKey = pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256'); |     return pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256'); | ||||||
|     var cipher = aes.createCipheriv('aes-128-gcm', aesKey, Buffer(iv)); |   } | ||||||
|  | 
 | ||||||
|  |   function encrypt(key, data, iv) { | ||||||
|  |     var cipher = aes.createCipheriv('aes-128-gcm', Buffer(key), Buffer(iv)); | ||||||
| 
 | 
 | ||||||
|     return Buffer.concat([cipher.update(Buffer(data)), cipher.final(), cipher.getAuthTag()]); |     return Buffer.concat([cipher.update(Buffer(data)), cipher.final(), cipher.getAuthTag()]); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function decrypt(data, password, salt, iv) { |   function decrypt(key, data, iv) { | ||||||
|     var aesKey = pbkdf2.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256'); |     var decipher = aes.createDecipheriv('aes-128-gcm', Buffer(key), Buffer(iv)); | ||||||
|     var decipher = aes.createDecipheriv('aes-128-gcm', aesKey, Buffer(iv)); |  | ||||||
| 
 | 
 | ||||||
|     decipher.setAuthTag(Buffer(data.slice(-16))); |     decipher.setAuthTag(Buffer(data.slice(-16))); | ||||||
|     return Buffer.concat([decipher.update(Buffer(data.slice(0, -16))), decipher.final()]); |     return Buffer.concat([decipher.update(Buffer(data.slice(0, -16))), decipher.final()]); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function convertBN(bn) { |   function bnToB64(bn) { | ||||||
|     if (bn.red) { |     if (bn.red) { | ||||||
|       bn = bn.fromRed(); |       bn = bn.fromRed(); | ||||||
|     } |     } | ||||||
| @ -39,21 +41,31 @@ | |||||||
|       key_ops: ['verify'] |       key_ops: ['verify'] | ||||||
|     , kty: 'EC' |     , kty: 'EC' | ||||||
|     , crv: 'P-256' |     , crv: 'P-256' | ||||||
|     , x: convertBN(key.getPublic().x) |     , x: bnToB64(key.getPublic().x) | ||||||
|     , y: convertBN(key.getPublic().y) |     , y: bnToB64(key.getPublic().y) | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     var privJwk = JSON.parse(JSON.stringify(pubJwk)); |     var privJwk = JSON.parse(JSON.stringify(pubJwk)); | ||||||
|     privJwk.key_ops = ['sign']; |     privJwk.key_ops = ['sign']; | ||||||
|     privJwk.d = convertBN(key.getPrivate()); |     privJwk.d = bnToB64(key.getPrivate()); | ||||||
| 
 | 
 | ||||||
|     return {privateKey: privJwk, publicKey: pubJwk}; |     return {privateKey: privJwk, publicKey: pubJwk}; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   function bnToBuffer(bn, size) { | ||||||
|  |     var buf = bn.toArrayLike(Buffer); | ||||||
|  |     if (!size || buf.length === size) { | ||||||
|  |       return buf; | ||||||
|  |     } | ||||||
|  |     if (buf > size) { | ||||||
|  |       throw new Error("EC signature number bigger than expected"); | ||||||
|  |     } | ||||||
|  |     return Buffer.concat([Buffer(size-buf.length).fill(0), buf]); | ||||||
|  |   } | ||||||
|   function sign(jwk, msg) { |   function sign(jwk, msg) { | ||||||
|     var key = ec.keyFromPrivate(Buffer(jwk.d, 'base64')); |     var key = ec.keyFromPrivate(Buffer(jwk.d, 'base64')); | ||||||
|     var sig = key.sign(sha256(msg)); |     var sig = key.sign(sha256(msg)); | ||||||
|     return Buffer.concat([Buffer(sig.r, 'hex'), Buffer(sig.s, 'hex')]); |     return Buffer.concat([bnToBuffer(sig.r, 32), bnToBuffer(sig.s, 32)]); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function verify(jwk, msg, signature) { |   function verify(jwk, msg, signature) { | ||||||
| @ -65,10 +77,19 @@ | |||||||
|     return key.verify(sha256(msg), sig); |     return key.verify(sha256(msg), sig); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exports.sha256  = function () { return Promise.resolve(sha256.apply(this, arguments)); }; |   function promiseWrap(func) { | ||||||
|   exports.encrypt = function () { return Promise.resolve(encrypt.apply(this, arguments)); }; |     return function() { | ||||||
|   exports.decrypt = function () { return Promise.resolve(decrypt.apply(this, arguments)); }; |       var args = arguments; | ||||||
|   exports.sign    = function () { return Promise.resolve(sign.apply(this, arguments)); }; |       return new Promise(function (resolve) { | ||||||
|   exports.verify  = function () { return Promise.resolve(verify.apply(this, arguments)); }; |         resolve(func.apply(null, args)); | ||||||
|   exports.genEcdsaKeyPair = function () { return Promise.resolve(genEcdsaKeyPair.apply(this, arguments)); }; |       }); | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   exports.sha256  = promiseWrap(sha256); | ||||||
|  |   exports.pbkdf2  = promiseWrap(runPbkdf2); | ||||||
|  |   exports.encrypt = promiseWrap(encrypt); | ||||||
|  |   exports.decrypt = promiseWrap(decrypt); | ||||||
|  |   exports.sign    = promiseWrap(sign); | ||||||
|  |   exports.verify  = promiseWrap(verify); | ||||||
|  |   exports.genEcdsaKeyPair = promiseWrap(genEcdsaKeyPair); | ||||||
| }()); | }()); | ||||||
| @ -9,12 +9,12 @@ | |||||||
|   var rename = require('gulp-rename'); |   var rename = require('gulp-rename'); | ||||||
| 
 | 
 | ||||||
|   gulp.task('default', function () { |   gulp.task('default', function () { | ||||||
|     return browserify('./browserify/crypto-index.js', {standalone: 'OAUTH3_crypto'}).bundle() |     return browserify('./browserify/crypto.fallback.js', {standalone: 'OAUTH3_crypto_fallback'}).bundle() | ||||||
|       .pipe(source('browserify/crypto-index.js')) |       .pipe(source('browserify/crypto.fallback.js')) | ||||||
|       .pipe(rename('oauth3.crypto.js')) |       .pipe(rename('oauth3.crypto.fallback.js')) | ||||||
|       .pipe(gulp.dest('./')) |       .pipe(gulp.dest('./')) | ||||||
|       .pipe(streamify(uglify())) |       .pipe(streamify(uglify())) | ||||||
|       .pipe(rename('oauth3.crypto.min.js')) |       .pipe(rename('oauth3.crypto.fallback.min.js')) | ||||||
|       .pipe(gulp.dest('./')) |       .pipe(gulp.dest('./')) | ||||||
|       ; |       ; | ||||||
|   }); |   }); | ||||||
|  | |||||||
							
								
								
									
										305
									
								
								oauth3.crypto.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								oauth3.crypto.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,305 @@ | |||||||
|  | ;(function (exports) { | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  |   var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||||
|  | 
 | ||||||
|  |   var loadFallback = function() { | ||||||
|  |     var prom; | ||||||
|  |     loadFallback = function () { return prom; }; | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   var webCrypto = {}; | ||||||
|  |   webCrypto.sha256 = function (buf) { | ||||||
|  |     return 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.encrypt = function (rawKey, data, iv) { | ||||||
|  |     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, data, iv) { | ||||||
|  |     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.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.sign = function (jwk, msg) { | ||||||
|  |     return crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) | ||||||
|  |       .then(function (key) { | ||||||
|  |         return 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 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); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   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; | ||||||
|  |             } | ||||||
|  |             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, dataBuf, zeroBuf.slice(0, 12)); | ||||||
|  |     }); | ||||||
|  |     checkResult('decrypt', OAUTH3._base64.bufferToUrlSafe(dataBuf), function () { | ||||||
|  |       return webCrypto.decrypt(keyBuf, encBuf, zeroBuf.slice(0, 12)); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     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(); | ||||||
|  | 
 | ||||||
|  |   OAUTH3.crypto.fingerprintJWK = function (jwk) { | ||||||
|  |     var keys; | ||||||
|  |     if (jwk.kty === 'EC') { | ||||||
|  |       keys = ['crv', 'x', 'y']; | ||||||
|  |     } else if (jwk.kty === 'RSA') { | ||||||
|  |       keys = ['e', 'n']; | ||||||
|  |     } else if (jwk.kty === 'oct') { | ||||||
|  |       keys = ['k']; | ||||||
|  |     } else { | ||||||
|  |       return OAUTH3.PromiseA.reject(new Error('invalid JWK key type ' + jwk.kty)); | ||||||
|  |     } | ||||||
|  |     keys.push('kty'); | ||||||
|  |     keys.sort(); | ||||||
|  | 
 | ||||||
|  |     var missing = keys.filter(function (name) { return !jwk.hasOwnProperty(name); }); | ||||||
|  |     if (missing.length > 0) { | ||||||
|  |       return OAUTH3.PromiseA.reject(new Error('JWK of type '+jwk.kty+' missing fields ' + missing)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}'; | ||||||
|  |     return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(jwkStr)) | ||||||
|  |     .then(OAUTH3._base64.bufferToUrlSafe); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   OAUTH3.crypto._createKey = function (ppid) { | ||||||
|  |     var kekPromise, ecdsaPromise, secretPromise; | ||||||
|  |     var salt = window.crypto.getRandomValues(new Uint8Array(16)); | ||||||
|  | 
 | ||||||
|  |     kekPromise = window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) | ||||||
|  |     .then(function (key) { | ||||||
|  |       var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; | ||||||
|  |       return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['encrypt']); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     ecdsaPromise = window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) | ||||||
|  |     .then(function (keyPair) { | ||||||
|  |       function tweakJWK(jwk) { | ||||||
|  |         return OAUTH3.crypto.fingerprintJWK(jwk).then(function (kid) { | ||||||
|  |           delete jwk.ext; | ||||||
|  |           jwk.alg = 'ES256'; | ||||||
|  |           jwk.kid = kid; | ||||||
|  |           return jwk; | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       return OAUTH3.PromiseA.all([ | ||||||
|  |         window.crypto.subtle.exportKey('jwk', keyPair.privateKey).then(tweakJWK) | ||||||
|  |       , window.crypto.subtle.exportKey('jwk', keyPair.publicKey).then(tweakJWK) | ||||||
|  |       ]).then(function (jwkPair) { | ||||||
|  |         return { | ||||||
|  |           privateKey: jwkPair[0] | ||||||
|  |         , publicKey:  jwkPair[1] | ||||||
|  |         }; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     secretPromise = window.crypto.subtle.generateKey({name: 'AES-GCM', length: 128}, true, ['encrypt', 'decrypt']) | ||||||
|  |     .then(function (key) { | ||||||
|  |       return window.crypto.subtle.exportKey('jwk', key); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise, secretPromise]).then(function (keys) { | ||||||
|  |       var ecdsaJwk  = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[1].privateKey)); | ||||||
|  |       var secretJwk = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[2])); | ||||||
|  |       var ecdsaIv  = window.crypto.getRandomValues(new Uint8Array(12)); | ||||||
|  |       var secretIv = window.crypto.getRandomValues(new Uint8Array(12)); | ||||||
|  | 
 | ||||||
|  |       return OAUTH3.PromiseA.all([ | ||||||
|  |         window.crypto.subtle.encrypt({name: 'AES-GCM', iv: ecdsaIv}, keys[0], ecdsaJwk) | ||||||
|  |       , window.crypto.subtle.encrypt({name: 'AES-GCM', iv: secretIv}, keys[0], secretJwk) | ||||||
|  |       ]) | ||||||
|  |       .then(function (encrypted) { | ||||||
|  |         return { | ||||||
|  |           publicKey:  keys[1].publicKey | ||||||
|  |         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) | ||||||
|  |         , userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) | ||||||
|  |         , salt:       OAUTH3._base64.bufferToUrlSafe(salt) | ||||||
|  |         , ecdsaIv:    OAUTH3._base64.bufferToUrlSafe(ecdsaIv) | ||||||
|  |         , secretIv:   OAUTH3._base64.bufferToUrlSafe(secretIv) | ||||||
|  |         }; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   OAUTH3.crypto._decryptKey = function (ppid, storedObj) { | ||||||
|  |     var salt   = OAUTH3._base64.urlSafeToBuffer(storedObj.salt); | ||||||
|  |     var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); | ||||||
|  |     var iv     = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); | ||||||
|  | 
 | ||||||
|  |     return window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) | ||||||
|  |     .then(function (key) { | ||||||
|  |       var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; | ||||||
|  |       return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['decrypt']); | ||||||
|  |     }) | ||||||
|  |     .then(function (key) { | ||||||
|  |       return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk); | ||||||
|  |     }) | ||||||
|  |     .then(OAUTH3._binStr.bufferToBinStr) | ||||||
|  |     .then(JSON.parse) | ||||||
|  |     .then(function (jwk) { | ||||||
|  |       return window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) | ||||||
|  |       .then(function (key) { | ||||||
|  |         key.kid = jwk.kid; | ||||||
|  |         key.alg = jwk.alg; | ||||||
|  |         return key; | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   OAUTH3.crypto._getKey = function (ppid) { | ||||||
|  |     return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(ppid)) | ||||||
|  |     .then(function (hash) { | ||||||
|  |       var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash); | ||||||
|  |       var promise; | ||||||
|  | 
 | ||||||
|  |       if (window.localStorage.getItem(name) === null) { | ||||||
|  |         promise = OAUTH3.crypto._createKey(ppid).then(function (key) { | ||||||
|  |           window.localStorage.setItem(name, JSON.stringify(key)); | ||||||
|  |           return key; | ||||||
|  |         }); | ||||||
|  |       } else { | ||||||
|  |         promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name))); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return promise.then(function (storedObj) { | ||||||
|  |         return OAUTH3.crypto._decryptKey(ppid, storedObj); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   OAUTH3.crypto._signPayload = function (payload) { | ||||||
|  |     return OAUTH3.crypto._getKey('some PPID').then(function (key) { | ||||||
|  |       var header = {type: 'JWT', alg: key.alg, kid: key.kid}; | ||||||
|  |       var input = [ | ||||||
|  |         OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) | ||||||
|  |       , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) | ||||||
|  |       ].join('.'); | ||||||
|  | 
 | ||||||
|  |       return window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3._binStr.binStrToBuffer(input)) | ||||||
|  |       .then(function (signature) { | ||||||
|  |         return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  | }('undefined' !== typeof exports ? exports : window)); | ||||||
| @ -3,151 +3,6 @@ | |||||||
| 
 | 
 | ||||||
|   var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; |   var OAUTH3 = exports.OAUTH3 = exports.OAUTH3 || require('./oauth3.core.js').OAUTH3; | ||||||
| 
 | 
 | ||||||
|   OAUTH3.crypto = {}; |  | ||||||
|   OAUTH3.crypto.fingerprintJWK = function (jwk) { |  | ||||||
|     var keys; |  | ||||||
|     if (jwk.kty === 'EC') { |  | ||||||
|       keys = ['crv', 'x', 'y']; |  | ||||||
|     } else if (jwk.kty === 'RSA') { |  | ||||||
|       keys = ['e', 'n']; |  | ||||||
|     } else if (jwk.kty === 'oct') { |  | ||||||
|       keys = ['k']; |  | ||||||
|     } else { |  | ||||||
|       return OAUTH3.PromiseA.reject(new Error('invalid JWK key type ' + jwk.kty)); |  | ||||||
|     } |  | ||||||
|     keys.push('kty'); |  | ||||||
|     keys.sort(); |  | ||||||
| 
 |  | ||||||
|     var missing = keys.filter(function (name) { return !jwk.hasOwnProperty(name); }); |  | ||||||
|     if (missing.length > 0) { |  | ||||||
|       return OAUTH3.PromiseA.reject(new Error('JWK of type '+jwk.kty+' missing fields ' + missing)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     var jwkStr = '{' + keys.map(function (name) { return name+':'+jwk[name]; }).join(',') + '}'; |  | ||||||
|     return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(jwkStr)) |  | ||||||
|     .then(OAUTH3._base64.bufferToUrlSafe); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   OAUTH3.crypto._createKey = function (ppid) { |  | ||||||
|     var kekPromise, ecdsaPromise, secretPromise; |  | ||||||
|     var salt = window.crypto.getRandomValues(new Uint8Array(16)); |  | ||||||
| 
 |  | ||||||
|     kekPromise = window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) |  | ||||||
|     .then(function (key) { |  | ||||||
|       var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; |  | ||||||
|       return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['encrypt']); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     ecdsaPromise = window.crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) |  | ||||||
|     .then(function (keyPair) { |  | ||||||
|       function tweakJWK(jwk) { |  | ||||||
|         return OAUTH3.crypto.fingerprintJWK(jwk).then(function (kid) { |  | ||||||
|           delete jwk.ext; |  | ||||||
|           jwk.alg = 'ES256'; |  | ||||||
|           jwk.kid = kid; |  | ||||||
|           return jwk; |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|       return OAUTH3.PromiseA.all([ |  | ||||||
|         window.crypto.subtle.exportKey('jwk', keyPair.privateKey).then(tweakJWK) |  | ||||||
|       , window.crypto.subtle.exportKey('jwk', keyPair.publicKey).then(tweakJWK) |  | ||||||
|       ]).then(function (jwkPair) { |  | ||||||
|         return { |  | ||||||
|           privateKey: jwkPair[0] |  | ||||||
|         , publicKey:  jwkPair[1] |  | ||||||
|         }; |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     secretPromise = window.crypto.subtle.generateKey({name: 'AES-GCM', length: 128}, true, ['encrypt', 'decrypt']) |  | ||||||
|     .then(function (key) { |  | ||||||
|       return window.crypto.subtle.exportKey('jwk', key); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     return OAUTH3.PromiseA.all([kekPromise, ecdsaPromise, secretPromise]).then(function (keys) { |  | ||||||
|       var ecdsaJwk  = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[1].privateKey)); |  | ||||||
|       var secretJwk = OAUTH3._binStr.binStrToBuffer(JSON.stringify(keys[2])); |  | ||||||
|       var ecdsaIv  = window.crypto.getRandomValues(new Uint8Array(12)); |  | ||||||
|       var secretIv = window.crypto.getRandomValues(new Uint8Array(12)); |  | ||||||
| 
 |  | ||||||
|       return OAUTH3.PromiseA.all([ |  | ||||||
|         window.crypto.subtle.encrypt({name: 'AES-GCM', iv: ecdsaIv}, keys[0], ecdsaJwk) |  | ||||||
|       , window.crypto.subtle.encrypt({name: 'AES-GCM', iv: secretIv}, keys[0], secretJwk) |  | ||||||
|       ]) |  | ||||||
|       .then(function (encrypted) { |  | ||||||
|         return { |  | ||||||
|           publicKey:  keys[1].publicKey |  | ||||||
|         , privateKey: OAUTH3._base64.bufferToUrlSafe(encrypted[0]) |  | ||||||
|         , userSecret: OAUTH3._base64.bufferToUrlSafe(encrypted[1]) |  | ||||||
|         , salt:       OAUTH3._base64.bufferToUrlSafe(salt) |  | ||||||
|         , ecdsaIv:    OAUTH3._base64.bufferToUrlSafe(ecdsaIv) |  | ||||||
|         , secretIv:   OAUTH3._base64.bufferToUrlSafe(secretIv) |  | ||||||
|         }; |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   OAUTH3.crypto._decryptKey = function (ppid, storedObj) { |  | ||||||
|     var salt   = OAUTH3._base64.urlSafeToBuffer(storedObj.salt); |  | ||||||
|     var encJwk = OAUTH3._base64.urlSafeToBuffer(storedObj.privateKey); |  | ||||||
|     var iv     = OAUTH3._base64.urlSafeToBuffer(storedObj.ecdsaIv); |  | ||||||
| 
 |  | ||||||
|     return window.crypto.subtle.importKey('raw', OAUTH3._binStr.binStrToBuffer(ppid), {name: 'PBKDF2'}, false, ['deriveKey']) |  | ||||||
|     .then(function (key) { |  | ||||||
|       var opts = {name: 'PBKDF2', salt: salt, iterations: 8192, hash: {name: 'SHA-256'}}; |  | ||||||
|       return window.crypto.subtle.deriveKey(opts, key, {name: 'AES-GCM', length: 128}, false, ['decrypt']); |  | ||||||
|     }) |  | ||||||
|     .then(function (key) { |  | ||||||
|       return window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encJwk); |  | ||||||
|     }) |  | ||||||
|     .then(OAUTH3._binStr.bufferToBinStr) |  | ||||||
|     .then(JSON.parse) |  | ||||||
|     .then(function (jwk) { |  | ||||||
|       return window.crypto.subtle.importKey('jwk', jwk, {name: 'ECDSA', namedCurve: jwk.crv}, false, ['sign']) |  | ||||||
|       .then(function (key) { |  | ||||||
|         key.kid = jwk.kid; |  | ||||||
|         key.alg = jwk.alg; |  | ||||||
|         return key; |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   OAUTH3.crypto._getKey = function (ppid) { |  | ||||||
|     return window.crypto.subtle.digest({name: 'SHA-256'}, OAUTH3._binStr.binStrToBuffer(ppid)) |  | ||||||
|     .then(function (hash) { |  | ||||||
|       var name = 'kek-' + OAUTH3._base64.bufferToUrlSafe(hash); |  | ||||||
|       var promise; |  | ||||||
| 
 |  | ||||||
|       if (window.localStorage.getItem(name) === null) { |  | ||||||
|         promise = OAUTH3.crypto._createKey(ppid).then(function (key) { |  | ||||||
|           window.localStorage.setItem(name, JSON.stringify(key)); |  | ||||||
|           return key; |  | ||||||
|         }); |  | ||||||
|       } else { |  | ||||||
|         promise = OAUTH3.PromiseA.resolve(JSON.parse(window.localStorage.getItem(name))); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       return promise.then(function (storedObj) { |  | ||||||
|         return OAUTH3.crypto._decryptKey(ppid, storedObj); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   OAUTH3.crypto._signPayload = function (payload) { |  | ||||||
|     return OAUTH3.crypto._getKey('some PPID').then(function (key) { |  | ||||||
|       var header = {type: 'JWT', alg: key.alg, kid: key.kid}; |  | ||||||
|       var input = [ |  | ||||||
|         OAUTH3._base64.encodeUrlSafe(JSON.stringify(header, null)) |  | ||||||
|       , OAUTH3._base64.encodeUrlSafe(JSON.stringify(payload, null)) |  | ||||||
|       ].join('.'); |  | ||||||
| 
 |  | ||||||
|       return window.crypto.subtle.sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, OAUTH3._binStr.binStrToBuffer(input)) |  | ||||||
|       .then(function (signature) { |  | ||||||
|         return input + '.' + OAUTH3._base64.bufferToUrlSafe(signature); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   OAUTH3.authn.resourceOwnerPassword = OAUTH3.authz.resourceOwnerPassword = function (directive, opts) { |   OAUTH3.authn.resourceOwnerPassword = OAUTH3.authz.resourceOwnerPassword = function (directive, opts) { | ||||||
|     var providerUri = directive.issuer; |     var providerUri = directive.issuer; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,18 +8,17 @@ | |||||||
|     "install": "./node_modules/.bin/gulp" |     "install": "./node_modules/.bin/gulp" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "atob": "^2.0.3", |  | ||||||
|     "browserify": "^14.1.0", |  | ||||||
|     "browserify-aes": "^1.0.6", |     "browserify-aes": "^1.0.6", | ||||||
|     "btoa": "^1.1.2", |  | ||||||
|     "create-hash": "^1.1.2", |     "create-hash": "^1.1.2", | ||||||
|     "elliptic": "^6.4.0", |     "elliptic": "^6.4.0", | ||||||
|  |     "pbkdf2": "^3.0.9", | ||||||
|  | 
 | ||||||
|  |     "browserify": "^14.1.0", | ||||||
|     "gulp": "^3.9.1", |     "gulp": "^3.9.1", | ||||||
|     "gulp-cli": "^1.2.2", |     "gulp-cli": "^1.2.2", | ||||||
|     "gulp-rename": "^1.2.2", |     "gulp-rename": "^1.2.2", | ||||||
|     "gulp-streamify": "^1.0.2", |     "gulp-streamify": "^1.0.2", | ||||||
|     "gulp-uglify": "^2.1.0", |     "gulp-uglify": "^2.1.0", | ||||||
|     "pbkdf2": "^3.0.9", |  | ||||||
|     "vinyl-source-stream": "^1.1.0" |     "vinyl-source-stream": "^1.1.0" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user