implemented the core crypto functions for node
This commit is contained in:
		
							parent
							
								
									9cfd517880
								
							
						
					
					
						commit
						ac89cb7904
					
				| @ -28,11 +28,20 @@ | ||||
|     return Buffer.concat([decipher.update(Buffer(data.slice(0, -16))), decipher.final()]); | ||||
|   } | ||||
| 
 | ||||
|   function bnToB64(bn) { | ||||
|     if (bn.red) { | ||||
|       bn = bn.fromRed(); | ||||
|   function bnToBuffer(bn, size) { | ||||
|     var buf = bn.toArrayLike(Buffer); | ||||
| 
 | ||||
|     if (!size || buf.length === size) { | ||||
|       return buf; | ||||
|     } else if (buf.length < size) { | ||||
|       return Buffer.concat([Buffer(size-buf.length).fill(0), buf]); | ||||
|     } else if (buf.length > size) { | ||||
|       throw new Error('EC signature number bigger than expected'); | ||||
|     } | ||||
|     var b64 = bn.toArrayLike(Buffer).toString('base64'); | ||||
|     throw new Error('invalid size "'+size+'" converting BigNumber to Buffer'); | ||||
|   } | ||||
|   function bnToB64(bn) { | ||||
|     var b64 = bnToBuffer(bn).toString('base64'); | ||||
|     return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, ''); | ||||
|   } | ||||
|   function genEcdsaKeyPair() { | ||||
| @ -41,8 +50,8 @@ | ||||
|       key_ops: ['verify'] | ||||
|     , kty: 'EC' | ||||
|     , crv: 'P-256' | ||||
|     , x: bnToB64(key.getPublic().x) | ||||
|     , y: bnToB64(key.getPublic().y) | ||||
|     , x: bnToB64(key.getPublic().getX()) | ||||
|     , y: bnToB64(key.getPublic().getY()) | ||||
|     }; | ||||
| 
 | ||||
|     var privJwk = JSON.parse(JSON.stringify(pubJwk)); | ||||
| @ -52,16 +61,6 @@ | ||||
|     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) { | ||||
|     var key = ec.keyFromPrivate(Buffer(jwk.d, 'base64')); | ||||
|     var sig = key.sign(sha256(msg)); | ||||
| @ -80,7 +79,10 @@ | ||||
|   function promiseWrap(func) { | ||||
|     return function() { | ||||
|       var args = arguments; | ||||
|       return new Promise(function (resolve) { | ||||
|       // This fallback file should only be used when the browser doesn't support everything we
 | ||||
|       // need with WebCrypto. Since it is only used in the browser we should be able to assume
 | ||||
|       // that OAUTH3 has been placed in the global scope and that we can access it here.
 | ||||
|       return new OAUTH3.PromiseA(function (resolve) { | ||||
|         resolve(func.apply(null, args)); | ||||
|       }); | ||||
|     }; | ||||
|  | ||||
							
								
								
									
										335
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							
							
						
						
									
										335
									
								
								oauth3.crypto.js
									
									
									
									
									
								
							| @ -3,160 +3,171 @@ | ||||
| 
 | ||||
|   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, 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.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]; | ||||
|   try { | ||||
|     OAUTH3.crypto.core = require('./oauth3.node.crypto'); | ||||
|   } catch (error) { | ||||
|     OAUTH3.crypto.core = {}; | ||||
| 
 | ||||
|     // 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 OAUTH3._browser.window.crypto.subtle.digest({name: 'SHA-256'}, buf); | ||||
|     }; | ||||
| 
 | ||||
|     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']); | ||||
|         }) | ||||
|         .catch(function (err) { | ||||
|           console.warn('error with WebCrypto', name, '- using fallback', err); | ||||
|           loadFallback().then(function () { | ||||
|             OAUTH3.crypto.core[name] = OAUTH3_crypto_fallback[name]; | ||||
|           }); | ||||
|         .then(function (key) { | ||||
|           return OAUTH3._browser.window.crypto.subtle.exportKey('raw', key); | ||||
|         }); | ||||
|     } | ||||
|     function checkResult(name, expected, func) { | ||||
|       checkException(name, function () { | ||||
|         return func() | ||||
|           .then(function (result) { | ||||
|             if (typeof expected === typeof result) { | ||||
|               return result; | ||||
|     }; | ||||
| 
 | ||||
|     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 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 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 OAUTH3._browser.window.crypto.subtle.verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, signature, msg); | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     function checkWebCrypto() { | ||||
|       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(); | ||||
|             } | ||||
|             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 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 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); | ||||
|     }); | ||||
|     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)); | ||||
|     var saltProm = OAUTH3.crypto.core.randomBytes(16); | ||||
|     var kekProm = saltProm.then(function (salt) { | ||||
|       return OAUTH3.crypto.core.pbkdf2(ppid, salt); | ||||
|     }); | ||||
| 
 | ||||
|     kekPromise = 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) | ||||
|  | ||||
							
								
								
									
										106
									
								
								oauth3.node.crypto.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								oauth3.node.crypto.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | ||||
| ;(function () { | ||||
| 'use strict'; | ||||
| 
 | ||||
|   var crypto = require('crypto'); | ||||
|   var OAUTH3 = require('./oauth3.core.js').OAUTH3; | ||||
|   var ec = require('elliptic').ec('p256'); | ||||
| 
 | ||||
|   function randomBytes(size) { | ||||
|     return new OAUTH3.PromiseA(function (resolve, reject) { | ||||
|       crypto.randomBytes(size, function (err, buf) { | ||||
|         if (err) { | ||||
|           reject(err); | ||||
|         } else { | ||||
|           resolve(buf); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function sha256(buf) { | ||||
|     return crypto.createHash('sha256').update(buf).digest(); | ||||
|   } | ||||
| 
 | ||||
|   function pbkdf2(password, salt) { | ||||
|     // Derived AES key is 128 bit, and the function takes a size in bytes.
 | ||||
|     return crypto.pbkdf2Sync(password, Buffer(salt), 8192, 16, 'sha256'); | ||||
|   } | ||||
| 
 | ||||
|   function encrypt(key, iv, data) { | ||||
|     var cipher = crypto.createCipheriv('aes-128-gcm', Buffer(key), Buffer(iv)); | ||||
| 
 | ||||
|     return Buffer.concat([cipher.update(Buffer(data)), cipher.final(), cipher.getAuthTag()]); | ||||
|   } | ||||
| 
 | ||||
|   function decrypt(key, iv, data) { | ||||
|     var decipher = crypto.createDecipheriv('aes-128-gcm', Buffer(key), Buffer(iv)); | ||||
| 
 | ||||
|     decipher.setAuthTag(Buffer(data.slice(-16))); | ||||
|     return Buffer.concat([decipher.update(Buffer(data.slice(0, -16))), decipher.final()]); | ||||
|   } | ||||
| 
 | ||||
|   function bnToBuffer(bn, size) { | ||||
|     var buf = bn.toArrayLike(Buffer); | ||||
| 
 | ||||
|     if (!size || buf.length === size) { | ||||
|       return buf; | ||||
|     } else if (buf.length < size) { | ||||
|       return Buffer.concat([Buffer(size-buf.length).fill(0), buf]); | ||||
|     } else if (buf.length > size) { | ||||
|       throw new Error('EC signature number bigger than expected'); | ||||
|     } | ||||
|     throw new Error('invalid size "'+size+'" converting BigNumber to Buffer'); | ||||
|   } | ||||
|   function bnToB64(bn) { | ||||
|     var b64 = bnToBuffer(bn).toString('base64'); | ||||
|     return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, ''); | ||||
|   } | ||||
|   function genEcdsaKeyPair() { | ||||
|     var key = ec.genKeyPair(); | ||||
|     var pubJwk = { | ||||
|       key_ops: ['verify'] | ||||
|     , kty: 'EC' | ||||
|     , crv: 'P-256' | ||||
|     , x: bnToB64(key.getPublic().getX()) | ||||
|     , y: bnToB64(key.getPublic().getY()) | ||||
|     }; | ||||
| 
 | ||||
|     var privJwk = JSON.parse(JSON.stringify(pubJwk)); | ||||
|     privJwk.key_ops = ['sign']; | ||||
|     privJwk.d = bnToB64(key.getPrivate()); | ||||
| 
 | ||||
|     return {privateKey: privJwk, publicKey: pubJwk}; | ||||
|   } | ||||
| 
 | ||||
|   function sign(jwk, msg) { | ||||
|     var key = ec.keyFromPrivate(Buffer(jwk.d, 'base64')); | ||||
|     var sig = key.sign(sha256(msg)); | ||||
|     return Buffer.concat([bnToBuffer(sig.r, 32), bnToBuffer(sig.s, 32)]); | ||||
|   } | ||||
| 
 | ||||
|   function verify(jwk, msg, signature) { | ||||
|     var key = ec.keyFromPublic({x: Buffer(jwk.x, 'base64'), y: Buffer(jwk.y, 'base64')}); | ||||
|     var sig = { | ||||
|       r: Buffer(signature.slice(0, signature.length/2)) | ||||
|     , s: Buffer(signature.slice(signature.length/2)) | ||||
|     }; | ||||
|     return key.verify(sha256(msg), sig); | ||||
|   } | ||||
| 
 | ||||
|   function promiseWrap(func) { | ||||
|     return function() { | ||||
|       var args = arguments; | ||||
|       return new OAUTH3.PromiseA(function (resolve) { | ||||
|         resolve(func.apply(null, args)); | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
|   exports.sha256  = promiseWrap(sha256); | ||||
|   exports.pbkdf2  = promiseWrap(pbkdf2); | ||||
|   exports.encrypt = promiseWrap(encrypt); | ||||
|   exports.decrypt = promiseWrap(decrypt); | ||||
|   exports.sign    = promiseWrap(sign); | ||||
|   exports.verify  = promiseWrap(verify); | ||||
|   exports.genEcdsaKeyPair = promiseWrap(genEcdsaKeyPair); | ||||
|   exports.randomBytes = randomBytes; | ||||
| }()); | ||||
| @ -31,10 +31,12 @@ | ||||
|     "log", | ||||
|     "sign" | ||||
|   ], | ||||
|   "dependencies": { | ||||
|     "elliptic": "^6.4.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "browserify-aes": "^1.0.6", | ||||
|     "create-hash": "^1.1.2", | ||||
|     "elliptic": "^6.4.0", | ||||
|     "pbkdf2": "^3.0.9", | ||||
| 
 | ||||
|     "browserify": "^14.1.0", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user