9653 lines
349 KiB
JavaScript
Executable File
9653 lines
349 KiB
JavaScript
Executable File
//*******************************************************************************
|
|
//
|
|
// Copyright 2018 Microsoft
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
//*******************************************************************************
|
|
|
|
var msrCryptoVersion = "1.5.6";
|
|
var msrCrypto = msrCrypto || (function () {
|
|
|
|
"use strict";
|
|
|
|
var operations = {};
|
|
|
|
operations.register = function (operationType, algorithmName, functionToCall) {
|
|
|
|
if (!operations[operationType]) {
|
|
operations[operationType] = {};
|
|
}
|
|
|
|
var op = operations[operationType];
|
|
|
|
if (!op[algorithmName]) {
|
|
op[algorithmName] = functionToCall;
|
|
}
|
|
|
|
};
|
|
|
|
operations.exists = function (operationType, algorithmName) {
|
|
if (!operations[operationType]) {
|
|
return false;
|
|
}
|
|
|
|
return (operations[operationType][algorithmName]) ? true : false;
|
|
};
|
|
|
|
/// Store the URL for this script. We will need this later to instantiate
|
|
/// new web workers (if supported).
|
|
var scriptUrl = (function () {
|
|
|
|
if (typeof document !== "undefined") {
|
|
// Use error.stack to find out the name of this script
|
|
try {
|
|
throw new Error();
|
|
} catch (e) {
|
|
if (e.stack) {
|
|
var match = /\w+:\/\/(.+?\/)*.+\.js/.exec(e.stack);
|
|
return (match && match.length > 0) ? match[0] : null;
|
|
}
|
|
}
|
|
} else if (typeof self !== "undefined") {
|
|
// If this script is being run in a WebWorker, 'document' will not exist
|
|
// but we can use self.
|
|
return self.location.href;
|
|
}
|
|
|
|
// We must be running in an environment without document or self.
|
|
return null;
|
|
|
|
})();
|
|
|
|
// Indication if the user provided entropy into the entropy pool.
|
|
var fprngEntropyProvided = false;
|
|
|
|
// Support for webWorkers IE10+.
|
|
var webWorkerSupport = (typeof Worker !== "undefined");
|
|
|
|
// Is this script running in an instance of a webWorker?
|
|
var runningInWorkerInstance = (typeof importScripts !== "undefined");
|
|
|
|
// Typed Arrays support?
|
|
var typedArraySupport = (typeof Uint8Array !== "undefined");
|
|
|
|
// Property setter/getter support IE9+.
|
|
var setterSupport = (function () {
|
|
try {
|
|
Object.defineProperty({}, "oncomplete", {});
|
|
return true;
|
|
} catch (ex) {
|
|
return false;
|
|
}
|
|
}());
|
|
|
|
// Run in async mode (requires web workers) and user can override to sync mode
|
|
// by setting the .forceSync property to true on the subtle interface
|
|
// this can be changes 'on the fly'.
|
|
var asyncMode = webWorkerSupport;
|
|
|
|
var createProperty = function (parentObject, propertyName, initialValue, getterFunction, setterFunction) {
|
|
/// <param name="parentObject" type="Object"/>
|
|
/// <param name="propertyName" type="String"/>
|
|
/// <param name="initialValue" type="Object"/>
|
|
/// <param name="getterFunction" type="Function"/>
|
|
/// <param name="setterFunction" type="Function" optional="true"/>
|
|
|
|
if (!setterSupport) {
|
|
parentObject[propertyName] = initialValue;
|
|
return;
|
|
}
|
|
|
|
var setGet = {};
|
|
|
|
getterFunction && (setGet.get = getterFunction);
|
|
setterFunction && (setGet.set = setterFunction);
|
|
|
|
Object.defineProperty(
|
|
parentObject,
|
|
propertyName, setGet);
|
|
};
|
|
|
|
// Collection of hash functions for global availability.
|
|
// Each hashfunction will add itself to the collection as it is evaluated.
|
|
var msrcryptoHashFunctions = {};
|
|
|
|
var msrcryptoUtilities = (function () {
|
|
|
|
var encodingChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
|
|
var btoaSupport = (typeof btoa !== "undefined");
|
|
|
|
function toBase64(data, base64Url) {
|
|
/// <signature>
|
|
/// <summary>Converts byte data to Base64 string</summary>
|
|
/// <param name="data" type="Array">An array of bytes values (numbers from 0-255)</param>
|
|
/// <param name="base64Url" type="Boolean" optional="true">Converts to a Base64Url string if True (default = false)</param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Converts byte data to Base64 string</summary>
|
|
/// <param name="data" type="UInt8Array">A UInt8Array</param>
|
|
/// <param name="base64Url" type="Boolean" optional="true">Converts to a Base64Url string if True (default = false)</param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Converts text to Base64 string</summary>
|
|
/// <param name="data" type="String">Text string</param>
|
|
/// <param name="base64Url" type="Boolean" optional="true">Converts to a Base64Url string if True (default = false)</param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
|
|
var output = "";
|
|
|
|
if (!base64Url) {
|
|
base64Url = false;
|
|
}
|
|
|
|
// If the input is an array type, convert it to a string.
|
|
// The built-in btoa takes strings.
|
|
if (data.pop || data.subarray) {
|
|
data = String.fromCharCode.apply(null, data);
|
|
}
|
|
|
|
if (btoaSupport) {
|
|
output = btoa(data);
|
|
} else {
|
|
|
|
var char1, char2, char3, enc1, enc2, enc3, enc4;
|
|
var i;
|
|
|
|
for (i = 0; i < data.length; i += 3) {
|
|
|
|
// Get the next three chars.
|
|
char1 = data.charCodeAt(i);
|
|
char2 = data.charCodeAt(i + 1);
|
|
char3 = data.charCodeAt(i + 2);
|
|
|
|
// Encode three bytes over four 6-bit values.
|
|
// [A7,A6,A5,A4,A3,A2,A1,A0][B7,B6,B5,B4,B3,B2,B1,B0][C7,C6,C5,C4,C3,C2,C1,C0].
|
|
// [A7,A6,A5,A4,A3,A2][A1,A0,B7,B6,B5,B4][B3,B2,B1,B0,C7,C6][C5,C4,C3,C2,C1,C0].
|
|
|
|
// 'enc1' = high 6-bits from char1
|
|
enc1 = char1 >> 2;
|
|
// 'enc2' = 2 low-bits of char1 + 4 high-bits of char2
|
|
enc2 = ((char1 & 0x3) << 4) | (char2 >> 4);
|
|
// 'enc3' = 4 low-bits of char2 + 2 high-bits of char3
|
|
enc3 = ((char2 & 0xF) << 2) | (char3 >> 6);
|
|
// 'enc4' = 6 low-bits of char3
|
|
enc4 = char3 & 0x3F;
|
|
|
|
// 'char2' could be 'nothing' if there is only one char left to encode
|
|
// if so, set enc3 & enc4 to 64 as padding.
|
|
if (isNaN(char2)) {
|
|
enc3 = enc4 = 64;
|
|
|
|
// If there was only two chars to encode char3 will be 'nothing'
|
|
// set enc4 to 64 as padding.
|
|
} else if (isNaN(char3)) {
|
|
enc4 = 64;
|
|
}
|
|
|
|
// Lookup the base-64 value for each encoding.
|
|
output = output +
|
|
encodingChars.charAt(enc1) +
|
|
encodingChars.charAt(enc2) +
|
|
encodingChars.charAt(enc3) +
|
|
encodingChars.charAt(enc4);
|
|
}
|
|
}
|
|
|
|
if (base64Url) {
|
|
return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, "");
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function base64ToString(encodedString) {
|
|
/// <signature>
|
|
/// <summary>Converts a Base64/Base64Url string to a text</summary>
|
|
/// <param name="encodedString" type="String">A Base64/Base64Url encoded string</param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
|
|
if (btoaSupport) {
|
|
|
|
// This could be encoded as base64url (different from base64)
|
|
encodedString = encodedString.replace(/-/g, "+").replace(/_/g, "/");
|
|
|
|
// In case the padding is missing, add some.
|
|
while (encodedString.length % 4 !== 0) {
|
|
encodedString += "=";
|
|
}
|
|
|
|
return atob(encodedString);
|
|
}
|
|
|
|
return String.fromCharCode.apply(null, base64ToBytes(encodedString));
|
|
|
|
}
|
|
|
|
function base64ToBytes(encodedString) {
|
|
/// <signature>
|
|
/// <summary>Converts a Base64/Base64Url string to an Array</summary>
|
|
/// <param name="encodedString" type="String">A Base64/Base64Url encoded string</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
|
|
// This could be encoded as base64url (different from base64)
|
|
encodedString = encodedString.replace(/-/g, "+").replace(/_/g, "/");
|
|
|
|
// In case the padding is missing, add some.
|
|
while (encodedString.length % 4 !== 0) {
|
|
encodedString += "=";
|
|
}
|
|
|
|
var output = [];
|
|
var char1, char2, char3;
|
|
var enc1, enc2, enc3, enc4;
|
|
var i;
|
|
|
|
// Remove any chars not in the base-64 space.
|
|
encodedString = encodedString.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
|
|
|
for (i = 0; i < encodedString.length; i += 4) {
|
|
|
|
// Get 4 characters from the encoded string.
|
|
enc1 = encodingChars.indexOf(encodedString.charAt(i));
|
|
enc2 = encodingChars.indexOf(encodedString.charAt(i + 1));
|
|
enc3 = encodingChars.indexOf(encodedString.charAt(i + 2));
|
|
enc4 = encodingChars.indexOf(encodedString.charAt(i + 3));
|
|
|
|
// Convert four 6-bit values to three characters.
|
|
// [A7,A6,A5,A4,A3,A2][A1,A0,B7,B6,B5,B4][B3,B2,B1,B0,C7,C6][C5,C4,C3,C2,C1,C0].
|
|
// [A7,A6,A5,A4,A3,A2,A1,A0][B7,B6,B5,B4,B3,B2,B1,B0][C7,C6,C5,C4,C3,C2,C1,C0].
|
|
|
|
// 'char1' = all 6 bits of enc1 + 2 high-bits of enc2.
|
|
char1 = (enc1 << 2) | (enc2 >> 4);
|
|
// 'char2' = 4 low-bits of enc2 + 4 high-bits of enc3.
|
|
char2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
|
// 'char3' = 2 low-bits of enc3 + all 6 bits of enc4.
|
|
char3 = ((enc3 & 3) << 6) | enc4;
|
|
|
|
// Convert char1 to string character and append to output
|
|
output.push(char1);
|
|
|
|
// 'enc3' could be padding
|
|
// if so, 'char2' is ignored.
|
|
if (enc3 !== 64) {
|
|
output.push(char2);
|
|
}
|
|
|
|
// 'enc4' could be padding
|
|
// if so, 'char3' is ignored.
|
|
if (enc4 !== 64) {
|
|
output.push(char3);
|
|
}
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
function getObjectType(object) {
|
|
/// <signature>
|
|
/// <summary>Returns the name of an object type</summary>
|
|
/// <param name="object" type="Object"></param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
|
|
return Object.prototype.toString.call(object).slice(8, -1);
|
|
}
|
|
|
|
function bytesToHexString(bytes, separate) {
|
|
/// <signature>
|
|
/// <summary>Converts an Array of bytes values (0-255) to a Hex string</summary>
|
|
/// <param name="bytes" type="Array"/>
|
|
/// <param name="separate" type="Boolean" optional="true">Inserts a separator for display purposes (default = false)</param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
|
|
var result = "";
|
|
if (typeof separate === "undefined") {
|
|
separate = false;
|
|
}
|
|
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
|
|
if (separate && (i % 4 === 0) && i !== 0) {
|
|
result += "-";
|
|
}
|
|
|
|
var hexval = bytes[i].toString(16).toUpperCase();
|
|
// Add a leading zero if needed.
|
|
if (hexval.length === 1) {
|
|
result += "0";
|
|
}
|
|
|
|
result += hexval;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function bytesToInt32(bytes, index) {
|
|
/// <summary>
|
|
/// Converts four bytes to a 32-bit int
|
|
/// </summary>
|
|
/// <param name="bytes">The bytes to convert</param>
|
|
/// <param name="index" optional="true">Optional starting point</param>
|
|
/// <returns type="Number">32-bit number</returns>
|
|
index = (index || 0);
|
|
|
|
return (bytes[index] << 24) |
|
|
(bytes[index + 1] << 16) |
|
|
(bytes[index + 2] << 8) |
|
|
bytes[index + 3];
|
|
}
|
|
|
|
function stringToBytes(messageString) {
|
|
/// <signature>
|
|
/// <summary>Converts a String to an Array of byte values (0-255)</summary>
|
|
/// <param name="messageString" type="String"/>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
|
|
var bytes = new Array(messageString.length);
|
|
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
bytes[i] = messageString.charCodeAt(i);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
function hexToBytesArray(hexString) {
|
|
/// <signature>
|
|
/// <summary>Converts a Hex-String to an Array of byte values (0-255)</summary>
|
|
/// <param name="hexString" type="String"/>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
|
|
hexString = hexString.replace(/\-/g, "");
|
|
|
|
var result = [];
|
|
while (hexString.length >= 2) {
|
|
result.push(parseInt(hexString.substring(0, 2), 16));
|
|
hexString = hexString.substring(2, hexString.length);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function clone(object) {
|
|
/// <signature>
|
|
/// <summary>Creates a shallow clone of an Object</summary>
|
|
/// <param name="object" type="Object"/>
|
|
/// <returns type="Object" />
|
|
/// </signature>
|
|
|
|
var newObject = {};
|
|
for (var propertyName in object) {
|
|
if (object.hasOwnProperty(propertyName)) {
|
|
newObject[propertyName] = object[propertyName];
|
|
}
|
|
}
|
|
return newObject;
|
|
}
|
|
|
|
function unpackData(base64String, arraySize, toUint32s) {
|
|
/// <signature>
|
|
/// <summary>Unpacks Base64 encoded data into arrays of data.</summary>
|
|
/// <param name="base64String" type="String">Base64 encoded data</param>
|
|
/// <param name="arraySize" type="Number" optional="true">Break data into sub-arrays of a given length</param>
|
|
/// <param name="toUint32s" type="Boolean" optional="true">Treat data as 32-bit data instead of byte data</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
|
|
var bytes = base64ToBytes(base64String),
|
|
data = [],
|
|
i;
|
|
|
|
if (isNaN(arraySize)) {
|
|
return bytes;
|
|
} else {
|
|
for (i = 0; i < bytes.length; i += arraySize) {
|
|
data.push(bytes.slice(i, i + arraySize));
|
|
}
|
|
}
|
|
|
|
if (toUint32s) {
|
|
for (i = 0; i < data.length; i++) {
|
|
data[i] = (data[i][0] << 24) + (data[i][1] << 16) + (data[i][2] << 8) + data[i][3];
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function int32ToBytes(int32) {
|
|
/// <signature>
|
|
/// <summary>Converts a 32-bit number to an Array of 4 bytes</summary>
|
|
/// <param name="int32" type="Number">32-bit number</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
return [(int32 >>> 24) & 255, (int32 >>> 16) & 255, (int32 >>> 8) & 255, int32 & 255];
|
|
}
|
|
|
|
function int32ArrayToBytes(int32Array) {
|
|
/// <signature>
|
|
/// <summary>Converts an Array 32-bit numbers to an Array bytes</summary>
|
|
/// <param name="int32Array" type="Array">Array of 32-bit numbers</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
|
|
var result = [];
|
|
for (var i = 0; i < int32Array.length; i++) {
|
|
result = result.concat(int32ToBytes(int32Array[i]));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function xorVectors(a, b) {
|
|
/// <signature>
|
|
/// <summary>Exclusive OR (XOR) two arrays.</summary>
|
|
/// <param name="a" type="Array">Input array.</param>
|
|
/// <param name="b" type="Array">Input array.</param>
|
|
/// <returns type="Array">XOR of the two arrays. The length is minimum of the two input array lengths.</returns>
|
|
/// </signature>
|
|
|
|
var length = Math.min(a.length, b.length),
|
|
res = new Array(length);
|
|
for (var i = 0 ; i < length ; i += 1) {
|
|
res[i] = a[i] ^ b[i];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function getVector(length, fillValue) {
|
|
/// <signature>
|
|
/// <summary>Get an array filled with zeroes (or optional fillValue.)</summary>
|
|
/// <param name="length" type="Number">Requested array length.</param>
|
|
/// <param name="fillValue" type="Number" optional="true"></param>
|
|
/// <returns type="Array"></returns>
|
|
/// </signature>
|
|
|
|
// Use a default value of zero
|
|
fillValue || (fillValue = 0);
|
|
|
|
var res = new Array(length);
|
|
for (var i = 0; i < length; i += 1) {
|
|
res[i] = fillValue;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function toArray(typedArray) {
|
|
/// <signature>
|
|
/// <summary>Converts a UInt8Array to a regular JavaScript Array</summary>
|
|
/// <param name="typedArray" type="UInt8Array"></param>
|
|
/// <returns type="Array"></returns>
|
|
/// </signature>
|
|
|
|
// If undefined or null return an empty array
|
|
if (!typedArray) {
|
|
return [];
|
|
}
|
|
|
|
// If already an Array return it
|
|
if (typedArray.pop) {
|
|
return typedArray;
|
|
}
|
|
|
|
// If it's an ArrayBuffer, convert it to a Uint8Array first
|
|
if (typedArray.isView || getObjectType(typedArray) === "ArrayBuffer") {
|
|
typedArray = new Uint8Array(typedArray);
|
|
}
|
|
|
|
// A single element array will cause a new Array to be created with the length
|
|
// equal to the value of the single element. Not what we want.
|
|
// We'll return a new single element array with the single value.
|
|
return (typedArray.length === 1) ? [typedArray[0]] : Array.apply(null, typedArray);
|
|
}
|
|
|
|
function padEnd(array, value, finalLength) {
|
|
/// <signature>
|
|
/// <summary>Pads the end of an array with a specified value</summary>
|
|
/// <param name="array" type="Array"></param>
|
|
/// <param name="value" type="Number">The value to pad to the array</param>
|
|
/// <param name="finalLength" type="Number">The final resulting length with padding</param>
|
|
/// <returns type="Array"></returns>
|
|
/// </signature>
|
|
|
|
while (array.length < finalLength) {
|
|
array.push(value);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
function padFront(array, value, finalLength) {
|
|
/// <signature>
|
|
/// <summary>Pads the front of an array with a specified value</summary>
|
|
/// <param name="array" type="Array"></param>
|
|
/// <param name="value" type="Number">The value to pad to the array</param>
|
|
/// <param name="finalLength" type="Number">The final resulting length with padding</param>
|
|
/// <returns type="Array"></returns>
|
|
/// </signature>
|
|
|
|
while (array.length < finalLength) {
|
|
array.unshift(value);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
function arraysEqual(array1, array2) {
|
|
/// <signature>
|
|
/// <summary>Checks if two Arrays are equal by comparing their values.</summary>
|
|
/// <param name="array1" type="Array"></param>
|
|
/// <param name="array2" type="Array"></param>
|
|
/// <returns type="Array"></returns>
|
|
/// </signature>
|
|
|
|
var result = true;
|
|
|
|
if (array1.length !== array2.length) {
|
|
result = false;
|
|
}
|
|
|
|
for (var i = 0; i < array1.length; i++) {
|
|
if (array1[i] !== array2[i]) {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function verifyByteArray(array) {
|
|
/// <signature>
|
|
/// <summary>Verify that an Array contains only byte values (0-255)</summary>
|
|
/// <param name="array" type="Array"></param>
|
|
/// <returns type="Boolean">Returns true if all values are 0-255</returns>
|
|
/// </signature>
|
|
|
|
if (getObjectType(array) !== "Array") {
|
|
return false;
|
|
}
|
|
|
|
var element;
|
|
|
|
for (var i = 0; i < array.length; i++) {
|
|
|
|
element = array[i];
|
|
|
|
if (isNaN(element) || element < 0 || element > 255) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return {
|
|
toBase64: toBase64,
|
|
base64ToString: base64ToString,
|
|
base64ToBytes: base64ToBytes,
|
|
getObjectType: getObjectType,
|
|
bytesToHexString: bytesToHexString,
|
|
bytesToInt32: bytesToInt32,
|
|
stringToBytes: stringToBytes,
|
|
unpackData: unpackData,
|
|
hexToBytesArray: hexToBytesArray,
|
|
int32ToBytes: int32ToBytes,
|
|
int32ArrayToBytes: int32ArrayToBytes,
|
|
toArray: toArray,
|
|
arraysEqual: arraysEqual,
|
|
clone: clone,
|
|
xorVectors: xorVectors,
|
|
padEnd: padEnd,
|
|
padFront: padFront,
|
|
getVector: getVector,
|
|
verifyByteArray: verifyByteArray
|
|
};
|
|
|
|
})();
|
|
|
|
var msrcryptoWorker = (function () {
|
|
|
|
// If we're running in a webworker we need to postMessage to return our result
|
|
// otherwise just return the value as normal.
|
|
function returnResult(result) {
|
|
if (runningInWorkerInstance) {
|
|
self.postMessage(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return {
|
|
|
|
jsCryptoRunner: function ( e) {
|
|
|
|
var operation = e.data.operationType;
|
|
var result;
|
|
|
|
if (!operations.exists(operation, e.data.algorithm.name)) {
|
|
throw new Error("unregistered algorithm.");
|
|
}
|
|
|
|
var func = operations[operation][e.data.algorithm.name];
|
|
|
|
var p = e.data;
|
|
|
|
if (p.operationSubType === "process") {
|
|
func(p);
|
|
result = returnResult({ type: "process" });
|
|
} else {
|
|
result = returnResult(func(p));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
})();
|
|
|
|
// If this is running in a webworker we need self.onmessage to receive messages from
|
|
// the calling script.
|
|
// If we are in 'synchronous mode' (everything running in one script)
|
|
// we don't want to override self.onmessage.
|
|
if (runningInWorkerInstance) {
|
|
|
|
self.onmessage = function ( e) {
|
|
|
|
// When this worker first gets instantiated we will receive seed data
|
|
// for this workers prng.
|
|
if (e.data.prngSeed) {
|
|
var entropy = e.data.prngSeed;
|
|
msrcryptoPseudoRandom.init(entropy);
|
|
return;
|
|
}
|
|
|
|
// Process the crypto operation
|
|
msrcryptoWorker.jsCryptoRunner(e);
|
|
};
|
|
}
|
|
|
|
var msrcryptoJwk = (function () {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
function stringToArray(stringData) {
|
|
|
|
var result = [];
|
|
|
|
for (var i = 0; i < stringData.length; i++) {
|
|
result[i] = stringData.charCodeAt(i);
|
|
}
|
|
|
|
if (result[result.length - 1] === 0) {
|
|
result.pop();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getKeyType(keyHandle) {
|
|
|
|
var algType = keyHandle.algorithm.name.slice(0, 3).toLowerCase();
|
|
|
|
if (algType === "rsa") {
|
|
return "RSA";
|
|
}
|
|
|
|
if (algType === "ecd") {
|
|
return "EC";
|
|
}
|
|
|
|
return "oct";
|
|
}
|
|
|
|
var algorithmMap = {
|
|
|
|
hmac : function(algorithm) {
|
|
return "HS" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
|
|
},
|
|
|
|
"aes-cbc" : function(algorithm) {
|
|
return "A" + algorithm.length.toString() + "CBC";
|
|
},
|
|
|
|
"aes-gcm" : function(algorithm) {
|
|
return "A" + algorithm.length.toString() + "GCM";
|
|
},
|
|
|
|
"rsaes-pkcs1-v1_5": function (algorithm) {
|
|
|
|
return "RSA1_5";
|
|
},
|
|
|
|
"rsassa-pkcs1-v1_5": function (algorithm) {
|
|
|
|
return "RS" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
|
|
|
|
},
|
|
|
|
"rsa-oaep": function (algorithm) {
|
|
|
|
return "RS-OAEP-" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
|
|
},
|
|
|
|
"rsa-pss": function (algorithm) {
|
|
|
|
return "PS" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
|
|
},
|
|
|
|
"ecdsa": function (algorithm) {
|
|
|
|
return "EC-" + algorithm.namedCurve.substring(algorithm.namedCurve.indexOf('-') + 1);
|
|
},
|
|
|
|
"ecdh": function (algorithm) {
|
|
|
|
return "EC-" + algorithm.namedCurve.substring(algorithm.namedCurve.indexOf('-') + 1);
|
|
}
|
|
};
|
|
|
|
function keyToJwk(keyHandle, keyData) {
|
|
|
|
var key = {};
|
|
|
|
key.kty = getKeyType(keyHandle);
|
|
key.ext = keyHandle.extractable;
|
|
key.alg = algorithmMap[keyHandle.algorithm.name.toLowerCase()](keyHandle.algorithm);
|
|
key.key_ops = keyHandle.keyUsage;
|
|
|
|
// Using .pop to determine if a property value is an array.
|
|
if (keyData.pop) {
|
|
key.k = utils.toBase64(keyData, true);
|
|
} else {
|
|
// Convert the base64Url properties to byte arrays
|
|
for (var property in keyData) {
|
|
if (keyData[property].pop) {
|
|
key[property] = utils.toBase64(keyData[property], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (keyHandle.algorithm.namedCurve) {
|
|
key["crv"] = keyHandle.algorithm.namedCurve;
|
|
}
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
function keyToJwkOld(keyHandle, keyData) {
|
|
|
|
var key = {};
|
|
|
|
key.kty = getKeyType(keyHandle);
|
|
key.extractable = keyHandle.extractable;
|
|
|
|
// Using .pop to determine if a property value is an array.
|
|
if (keyData.pop) {
|
|
key.k = utils.toBase64(keyData, true);
|
|
} else {
|
|
// Convert the base64Url properties to byte arrays
|
|
for (var property in keyData) {
|
|
if (keyData[property].pop) {
|
|
key[property] = utils.toBase64(keyData[property], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (keyHandle.algorithm.namedCurve) {
|
|
key["crv"] = keyHandle.algorithm.namedCurve;
|
|
}
|
|
|
|
var stringData = JSON.stringify(key, null, '\t');
|
|
|
|
return stringToArray(stringData);
|
|
|
|
}
|
|
|
|
// 'jwkKeyData' is an array of bytes. Each byte is a charCode for a json key string
|
|
function jwkToKey(keyData, algorithm, propsToArray) {
|
|
|
|
// Convert the json string to an object
|
|
var jsonKeyObject = JSON.parse(JSON.stringify(keyData)); //JSON.parse(jsonString);
|
|
|
|
// Convert the base64url encoded properties to byte arrays
|
|
for (var i = 0; i < propsToArray.length; i += 1) {
|
|
var propValue = jsonKeyObject[propsToArray[i]];
|
|
if (propValue) {
|
|
jsonKeyObject[propsToArray[i]] =
|
|
utils.base64ToBytes(propValue);
|
|
}
|
|
}
|
|
|
|
return jsonKeyObject;
|
|
}
|
|
|
|
return {
|
|
keyToJwkOld: keyToJwkOld,
|
|
keyToJwk: keyToJwk,
|
|
jwkToKey: jwkToKey
|
|
};
|
|
})();
|
|
|
|
function msrcryptoMath() {
|
|
// 'number' of bits per digit. Must be even.
|
|
var DIGIT_BITS = 24;
|
|
// 'number' of bytes per digit.
|
|
var DIGIT_NUM_BYTES = Math.floor(DIGIT_BITS / 8);
|
|
// digit mask.
|
|
var DIGIT_MASK = (1 << DIGIT_BITS) - 1;
|
|
// digit base.
|
|
var DIGIT_BASE = (1 << DIGIT_BITS);
|
|
// max digit value, unsigned
|
|
var DIGIT_MAX = DIGIT_MASK;
|
|
|
|
// Construct scaler for DIGIT_NUM_BYTES, so I don't have to multiply in the loop
|
|
var DIGIT_SCALER = [1, 256];
|
|
for (var i = 2; i <= DIGIT_NUM_BYTES; i++) {
|
|
DIGIT_SCALER[i] = DIGIT_SCALER[i - 1] * 256;
|
|
}
|
|
|
|
// Number of trailing zero bits in numbers 0..15 (4 bits). [0] is for 0, [15] is for 15.
|
|
var Zero = [0];
|
|
var One = [1];
|
|
|
|
// Create an array, mimics the constructors for typed arrays.
|
|
function createArray( parameter) {
|
|
var i, array = null;
|
|
if (!arguments.length || typeof arguments[0] === "number") {
|
|
// A number.
|
|
array = new Array(parameter);
|
|
for (i = 0; i < parameter; i += 1) {
|
|
array[i] = 0;
|
|
}
|
|
} else if (typeof arguments[0] === "object") {
|
|
// An array or other index-able object
|
|
array = new Array(parameter.length);
|
|
for (i = 0; i < parameter.length; i += 1) {
|
|
array[i] = parameter[i];
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
function swapEndianness(bytes) {
|
|
/// <summary>Swap big endian bytes to little endian bytes.</summary>
|
|
/// <param name="bytes" type="Bytes">UInt8Array - representing a big-integer.</param>
|
|
/// <returns type="Bytes">UInt8Array - the number with endianness swapped.</returns>
|
|
|
|
var out = new Array(bytes.length);
|
|
var i = 0;
|
|
|
|
while (i < bytes.length) {
|
|
out[i] = bytes[bytes.length - i - 1];
|
|
i += 1;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
function stringToDigits(number, radix) {
|
|
/// <summary>Parse a String in a given base into a little endian digit array.</summary>
|
|
/// <param name="number" type="String">Input unsigned integer in a string.</param>
|
|
/// <param name="radix" optional="true" integer="true">
|
|
/// <![CDATA[ Radix of the input. Must be >=2 and <=36. Default = 10. ]]>
|
|
/// </param>
|
|
/// <returns type="Array">Array of digits in little endian; [0] is LSW.</returns>
|
|
|
|
// skip leading and trailing whitespace.
|
|
number = number.replace(/^\s+|\s+$/g, '');
|
|
var num = [0];
|
|
var buffer = [0];
|
|
radix = radix || 10; // default radix is 10
|
|
for (var i = 0; i < number.length; i += 1) {
|
|
// Extract character
|
|
var char = parseInt(number[i], radix);
|
|
if (isNaN(char)) {
|
|
throw new Error("Failed to convert string to integer in radix " + radix.toString());
|
|
}
|
|
|
|
// 'buffer' = 'num' * 'radix'
|
|
multiply(num, radix, buffer);
|
|
|
|
// 'num' = 'buffer' + 'char'
|
|
add(buffer, [ char], num);
|
|
normalizeDigitArray(num);
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
function digitsToString(digits, radix) {
|
|
/// <summary>Convert a big-endian byte array to a number in string in radix.</summary>
|
|
/// <param name="digits" type="Digits">A big integer as a little-endian digit array.</param>
|
|
/// <param name="radix" optional="true" integer="true">Radix from 2 to 26. Default = 10.</param>
|
|
/// <returns type="String">The number in base radix as a string.</returns>
|
|
|
|
radix = radix || 10;
|
|
if (DIGIT_BASE <= radix) {
|
|
throw new Error("DIGIT_BASE is smaller than RADIX; cannot convert.");
|
|
}
|
|
|
|
var wordLength = digits.length;
|
|
var quotient = [];
|
|
var remainder = [];
|
|
var temp1 = [];
|
|
var temp2 = [];
|
|
var divisor = [];
|
|
var a = [];
|
|
var i;
|
|
|
|
// Find the largest divisor that fits in a digit in radix
|
|
//divisor[0] = 10000; // Largest power of ten fitting in a digit
|
|
var sb = "";
|
|
var pad = "0";
|
|
divisor[0] = radix;
|
|
while (Math.floor(DIGIT_BASE / divisor[0]) >= radix) {
|
|
divisor[0] = divisor[0] * radix;
|
|
pad = pad.concat("0");
|
|
}
|
|
|
|
for (i = 0; i < wordLength; i += 1) {
|
|
a[i] = digits[i];
|
|
}
|
|
|
|
do {
|
|
var allZeros = true;
|
|
for (i = 0; i < a.length; i += 1) {
|
|
if (a[i] !== 0) {
|
|
allZeros = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allZeros) {
|
|
break;
|
|
}
|
|
|
|
divRem(a, divisor, quotient, remainder, temp1, temp2);
|
|
normalizeDigitArray(quotient, a.length, true);
|
|
|
|
var newDigits = remainder[0].toString(radix);
|
|
sb = pad.substring(0, pad.length - newDigits.length) + newDigits + sb;
|
|
|
|
var swap = a;
|
|
a = quotient;
|
|
quotient = swap;
|
|
} while (true);
|
|
|
|
// Trim leading zeros
|
|
while (sb.length !== 0 && sb[0] === "0") {
|
|
sb = sb.substring(1, sb.length);
|
|
}
|
|
|
|
if (sb.length === 0) {
|
|
sb = "0";
|
|
}
|
|
|
|
return sb;
|
|
}
|
|
|
|
function powerOfTwo(i) {
|
|
/// <summary>Given a positive integer i, return a big integer in big-endian format
|
|
/// equal to 2^i. This is useful for creating fields of certain size.</summary>
|
|
/// <param name="i" type="Number">A positive integer.</param>
|
|
/// <returns>UInt8Array - 2^i as a big-endian byte array.</returns>
|
|
var requiredBytes = Math.ceil((i + 1) / 8);
|
|
var out = createArray(requiredBytes);
|
|
out[0] = Math.pow(2, i % 8);
|
|
return out;
|
|
}
|
|
|
|
function computeBitArray(bytes) {
|
|
/// <summary>Given an array of bytes in big-endian format, compute UInt8Array with
|
|
/// one element for each bit (0 or 1), in little-endian order.</summary>
|
|
/// <param name="bytes" type="Digits">An array of bytes in big-endian format.</param>
|
|
/// <returns type="Digits">An array of 0's and 1's representing the bits in little-endian.</returns>
|
|
|
|
var out = createArray(bytes.length * 8);
|
|
var bitLength = 0;
|
|
var i = bytes.length - 1;
|
|
while (i >= 0) {
|
|
var j = 0;
|
|
while (j < 8) {
|
|
var mask = (1 << j);
|
|
var bit = ((bytes[i] & mask) === mask) ? 1 : 0;
|
|
var thisBitIndex = (8 * ((bytes.length - i) - 1)) + j;
|
|
|
|
if (bit === 1) {
|
|
bitLength = thisBitIndex + 1;
|
|
}
|
|
|
|
out[thisBitIndex] = bit;
|
|
j += 1;
|
|
}
|
|
|
|
i--;
|
|
}
|
|
|
|
return out.slice(0, bitLength);
|
|
}
|
|
|
|
function bitScanForward(value) {
|
|
/// <summary>Return the 0-based index of the first non-zero bit starting at the most significant bit position.</summary>
|
|
/// <param name="value" type="Number" integer="true">Value to scan.</param>
|
|
/// <returns>Zero-based index of the first non-zero bit.</returns>
|
|
var mask = DIGIT_BASE >>> 1;
|
|
var index = DIGIT_BITS;
|
|
while (index-- > 0) {
|
|
if ((value & mask) === mask) {
|
|
break;
|
|
}
|
|
mask = mask >>> 1;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
function highestSetBit(bytes) {
|
|
/// <summary>Returns the (1 indexed) index of the highest set bit.</summary>
|
|
/// <param name="bytes" type="Array">A big-endian big integer byte array.</param>
|
|
/// <returns type="Number">The index of the highest bit.</returns>
|
|
|
|
var i = 0;
|
|
var bitLength = 0;
|
|
|
|
while (i < bytes.length) {
|
|
if (bitLength === 0) {
|
|
// Look for highest set bit in this byte
|
|
var j = 7;
|
|
while (j >= 0 && bitLength === 0) {
|
|
var mask = (1 << j);
|
|
if ((bytes[i] & mask) === mask) {
|
|
bitLength = j + 1;
|
|
}
|
|
|
|
j--;
|
|
}
|
|
} else {
|
|
bitLength += 8;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
return bitLength;
|
|
}
|
|
|
|
function fixedWindowRecode(digits, windowSize, t) {
|
|
/// <summary></summary>
|
|
/// <param name="digits" type="Array">Digits to recode</param>
|
|
/// <param name="windowSize" type="Number">Window size</param>
|
|
/// <returns type="Array">Recoded digits</returns>}
|
|
|
|
// Make a copy of digits because we are going to modify it with shifts below.
|
|
digits = digits.slice();
|
|
|
|
var recodedDigits = [],
|
|
windowSizeBits = Math.pow(2, windowSize),
|
|
windowSizeMinus1Bits = Math.pow(2, windowSize - 1);
|
|
|
|
for (var i = 0; i < t; i++) {
|
|
|
|
// k_digits[i] := (Z!k mod 2^w) - 2^(w-1);
|
|
recodedDigits[i] = (digits[0] % windowSizeBits) - windowSizeMinus1Bits;
|
|
|
|
// k := (k - k_digits[i])/2^(w-1);
|
|
digits[0] = digits[0] - recodedDigits[i];
|
|
|
|
// PERF : can probably do this faster
|
|
cryptoMath.shiftRight(digits, digits, windowSize - 1);
|
|
}
|
|
|
|
recodedDigits[i] = digits[0];
|
|
|
|
return recodedDigits;
|
|
}
|
|
|
|
function fetchBits(digits, startBit, count) {
|
|
/// <summary>From an array of digits, return a sequential set of bits.</summary>
|
|
/// <param name="digits" type=""></param>
|
|
/// <param name="start" type=""></param>
|
|
/// <param name="end" type=""></param>
|
|
|
|
var startDigit = Math.floor(startBit / cryptoMath.DIGIT_BITS);
|
|
var endDigit = startDigit + 1;
|
|
|
|
var shiftRight = (startBit % cryptoMath.DIGIT_BITS);
|
|
var shiftLeft = cryptoMath.DIGIT_BITS - shiftRight;
|
|
|
|
var bits = (digits[startDigit] >>> shiftRight) | (digits[endDigit] << shiftLeft);
|
|
|
|
return bits & (cryptoMath.DIGIT_MASK >>> (cryptoMath.DIGIT_BITS - count));
|
|
|
|
}
|
|
|
|
function fetchBits2(digits, startBit, count) {
|
|
/// <summary>From an array of digits, return a sub-set of bits from an arbitray index.</summary>
|
|
/// <param name="digits" type="Array">Array of digits</param>
|
|
/// <param name="startBit" type="Number">Zero-index position of start bit</param>
|
|
/// <param name="count" type="Number">Number of bits to return</param>
|
|
/// <returns type="Number">Value of n-bits</returns>
|
|
|
|
var startDigit = Math.floor(startBit / DIGIT_BITS),
|
|
shiftRight = (startBit % DIGIT_BITS);
|
|
|
|
return (digits[startDigit] >>> shiftRight) |
|
|
(digits[startDigit + 1] << (DIGIT_BITS - shiftRight))
|
|
& (DIGIT_MASK >>> (DIGIT_BITS - count));
|
|
}
|
|
|
|
function copyArray( source, sourceIndex, destination, destIndex, length) {
|
|
/// <summary>Copies a range of elements from one array to another array.</summary>
|
|
/// <param name="source" type="Array">Source array to copy from.</param>
|
|
/// <param name="sourceIndex" type="Number">The index in the source array at which copying begins.</param>
|
|
/// <param name="destination" type="Array">The array that receives the data.</param>
|
|
/// <param name="destIndex" type="Number">The index in the destination array at which storing begins.</param>
|
|
/// <param name="length" type="Number">The number of elements to copy.</param>
|
|
while (length-- > 0) {
|
|
destination[destIndex + length] = source[sourceIndex + length];
|
|
}
|
|
}
|
|
|
|
function isZero(array) {
|
|
/// <summary>Check if an array is zero. All elements are zero.</summary>
|
|
/// <param name="array" type="Digits">UInt16Array - An array to be checked.</param>
|
|
/// <returns type="Boolean"/>
|
|
var i;
|
|
for (i = 0; i < array.length; i += 1) {
|
|
if (array[i] !== 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function isEven(array) {
|
|
/// <summary>Returns true if this number is even.</summary>
|
|
/// <param name="array" type="Digits"/>
|
|
/// <returns type="Boolean"/>
|
|
return (array[0] & 0x1) === 0x0;
|
|
}
|
|
|
|
function sequenceEqual(left, right) {
|
|
/// <summary>Compare two indexable collections for sequence equality.</summary>
|
|
/// <param name="left" type="Digits">The left array.</param>
|
|
/// <param name="right" type="Digits">The right array.</param>
|
|
/// <returns type="Boolean">True if both arrays are the same.</returns>
|
|
if (left.length !== right.length) {
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < left.length; i += 1) {
|
|
if (left[i] !== right[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function bytesToDigits(bytes) {
|
|
/// <summary>Convert an unsigned number from big-endian bytes to little endian digits.</summary>
|
|
/// <param name="bytes" type="Bytes">The number in unsigned big-endian byte format.</param>
|
|
/// <returns type="Array">The digits in little-endian.</returns>
|
|
|
|
// Construct scaler for DIGIT_NUM_BYTES, so I don't have to multiply in the loop
|
|
var arrayLength = Math.floor((bytes.length + DIGIT_NUM_BYTES - 1) / DIGIT_NUM_BYTES);
|
|
var array = new Array(arrayLength);
|
|
array[0] = 0;
|
|
var digit = 0, index = 0, scIndex = 0;
|
|
for (var i = bytes.length - 1; i >= 0; i--) {
|
|
digit = digit + (DIGIT_SCALER[scIndex++] * (bytes[i] & 0x0ff));
|
|
if (DIGIT_SCALER[scIndex] === DIGIT_BASE) {
|
|
scIndex = 0;
|
|
array[index++] = digit;
|
|
digit = 0;
|
|
}
|
|
}
|
|
|
|
// Last digit (MSW), if there is a need
|
|
if (digit !== 0) {
|
|
array[index] = digit;
|
|
}
|
|
|
|
// Replace potential undefined elements with zeros
|
|
while (array[--arrayLength] == null) array[arrayLength] = 0;
|
|
|
|
return array;
|
|
}
|
|
|
|
function digitsToBytes(digits, trim, minTrimLength) {
|
|
/// <summary>Construct a big endian array of bytes from a litte-endian array of digits.
|
|
/// Always returns at least one byte and trims leading zeros.</summary>
|
|
/// <param name="digits" type="Array">The digits in little-endian.</param>
|
|
/// <param name="trim" type="Boolean" optional="true">Remove the leading zeros from the result (default true)</param>
|
|
/// <param name="minTrimLength" type="Number" optional="true">Minimum length to trim down to. Valid only if trim is true. Default=1.</param>
|
|
/// <returns type="Array">Encoded bytes in big-endian format.</returns>
|
|
|
|
var i, j, byte1;
|
|
var bytes = [0];
|
|
|
|
if (typeof trim === "undefined") {
|
|
trim = true;
|
|
}
|
|
|
|
for (i = 0; i < digits.length; i += 1) {
|
|
byte1 = digits[i];
|
|
for (j = 0; j < DIGIT_NUM_BYTES; j += 1) {
|
|
bytes[i * DIGIT_NUM_BYTES + j] = byte1 & 0x0FF;
|
|
byte1 = Math.floor(byte1 / 256);
|
|
}
|
|
}
|
|
|
|
bytes = swapEndianness(bytes);
|
|
|
|
if (minTrimLength === undefined) {
|
|
minTrimLength = 1;
|
|
}
|
|
if (trim) {
|
|
while (bytes.length > minTrimLength && bytes[0] === 0) {
|
|
bytes.shift();
|
|
}
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
function intToDigits(value, numDigits) {
|
|
/// <summary>Construct an array of digits from a positive integer.</summary>
|
|
/// <param name="value" type="Number" integer="true">A positive integer to be converted to digit form.</param>
|
|
/// <param name="numDigits" type="Number" optional="true" integer="true">The number of digits to use for the digit form.</param>
|
|
/// <returns type="Array">The given integer in digit form.</returns>
|
|
|
|
if (typeof numDigits === "undefined") {
|
|
if (value <= 1) {
|
|
numDigits = 1; // Special case <= 1
|
|
} else {
|
|
var numBits = Math.log(value) / Math.LN2;
|
|
numDigits = Math.ceil(numBits / DIGIT_BITS);
|
|
}
|
|
}
|
|
|
|
var digitRepresentation = [];
|
|
while (value > 0) {
|
|
digitRepresentation.push(value % DIGIT_BASE);
|
|
value = Math.floor(value / DIGIT_BASE);
|
|
}
|
|
|
|
while (digitRepresentation.length < numDigits) {
|
|
digitRepresentation.push(0);
|
|
}
|
|
|
|
return digitRepresentation;
|
|
}
|
|
|
|
function mswIndex(digits) {
|
|
/// <summary>Return the index of the most significant word of x, 0-indexed.
|
|
/// If x is zero (no significant index), then -1 is returned.</summary>
|
|
/// <param name="digits" type="Array">Digit array.</param>
|
|
/// <returns type="Number">Index of the most significant word, or -1 if digits is zero.</returns>
|
|
for (var i = digits.length - 1; i >= 0; i--) {
|
|
if (digits[i] !== undefined && digits[i] !== 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return (digits[0] === 0) ? -1 : 0;
|
|
}
|
|
|
|
function compareDigits(left, right) {
|
|
/// <summary>Compare two digit arrays by value. Returns an integer indicating the comparison result.
|
|
/// Digit arrays are in little endian.</summary>
|
|
/// <param name="left" type="Array">The object on the left side of the comparison.</param>
|
|
/// <param name="right" type="Array">The object on the right side of the comparison.</param>
|
|
/// <returns type="Number">A value that indicates the relative order of the objects
|
|
/// being compared. The value is 0 if the items are equal,
|
|
/// negative if the left object precedes the right object,
|
|
/// and positive otherwise.</returns>
|
|
|
|
var comparisonResult = 0;
|
|
var nLeft = mswIndex(left) + 1;
|
|
var nRight = mswIndex(right) + 1;
|
|
if (nLeft > nRight) {
|
|
comparisonResult = 1;
|
|
} else if (nRight > nLeft) {
|
|
comparisonResult = -1;
|
|
} else {
|
|
while ((nLeft-- > 0) && (comparisonResult === 0)) {
|
|
comparisonResult = left[nLeft] - right[nLeft];
|
|
}
|
|
}
|
|
|
|
return comparisonResult;
|
|
}
|
|
|
|
function normalizeDigitArray(digits, length, pad) {
|
|
/// <summary>Normalize a digit array by truncating any leading zeroes and adjusting its length.
|
|
/// Set the length if given, and pad it with zeroes to that length of padding is requested.</summary>
|
|
/// <remarks>Normalization results with a zero-indexed length of the array such that the MSW is not zero.
|
|
/// If the final array length is zero and no non-zero digits are found, assign digits[0]=0 and set length to 1.
|
|
/// Optionally, pad with zeroes to the given length, and set the array length.</remarks>
|
|
/// <param name="digits" type="Array">Digit array.</param>
|
|
/// <param name="length" type="Number" integer="true" optional="true">Output length to pad with zeroes.</param>
|
|
/// <param name="pad" type="Boolean" optional="true">Pad with zeroes to length if true [false].</param>
|
|
/// <returns type="Array">Resized digits array; same input object.</returns>
|
|
|
|
// Trim. Find the trimmed length and the position to start padding from (if padding is requested).
|
|
var i = mswIndex(digits);
|
|
|
|
// set the length to the given length (if given) or the trimmed length
|
|
digits.length = length || i + 1;
|
|
|
|
// Pad to the length
|
|
if (pad) {
|
|
while (++i < digits.length) {
|
|
digits[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (digits.length <= 0) {
|
|
// no non-zero digits found.
|
|
digits[0] = 0;
|
|
digits.length = 1;
|
|
}
|
|
|
|
return digits;
|
|
}
|
|
|
|
function shiftRight(source, destination, bits, length) {
|
|
/// <summary>Shift a big integer to the right by the given number of bits or 1 if bits is not specified,
|
|
/// effectively dividing by two (or 2^bits) and ignoring the remainder.</summary>
|
|
/// <param name="source" type="Array">Source digit array.</param>
|
|
/// <param name="destination" type="Array">Destination digit array. May be the same as source.</param>
|
|
/// <param name="bits" integer="true" optional="true">Number of bits to shift, must be less than DIGIT_BITS and greater or equal to zero. Default is 1.</param>
|
|
/// <param name="length" optional="true" integer="true">Number of items to shift from he source array. Default is source.length.</param>
|
|
/// <remarks>This is a numerical shift. Integers are stored in arrays in little-endian format.
|
|
/// Thus, this function shifts an array from higher order indices into lower indices. [0] is LSW.
|
|
/// </remarks>
|
|
|
|
if (bits === undefined) {
|
|
bits = 1;
|
|
} else if (bits >= DIGIT_BITS || bits < 0) {
|
|
throw new Error("Invalid bit count for shiftRight");
|
|
}
|
|
if (length === undefined) {
|
|
length = source.length;
|
|
}
|
|
|
|
var n = length - 1;
|
|
var leftShiftBitCount = DIGIT_BITS - bits;
|
|
for (var i = 0; i < n; i++) {
|
|
destination[i] = ((source[i + 1] << leftShiftBitCount) | (source[i] >>> bits)) & DIGIT_MASK;
|
|
//a[i] = high|low = low bits of a[i+1] | high bits of a[i]
|
|
}
|
|
|
|
destination[n] = source[n] >>> bits;
|
|
}
|
|
|
|
function shiftLeft(source, destination, bits, length) {
|
|
/// <summary>Shift a number array to the left by given bits, i.e., multiply by 2^bits.</summary>
|
|
/// <param name="source" type="Array">Source digit array.</param>
|
|
/// <param name="destination" type="Array">Destination digit array. May be the same as source.</param>
|
|
/// <param name="bits" integer="true" optional="true">Number of bits to shift, must be less than DIGIT_BITS and greater or equal to zero. Default is 1.</param>
|
|
/// <param name="length" optional="true" integer="true">Number of items to shift from he source array. Default is source.length.</param>
|
|
/// <remarks>An additional MSW digit may be added if the leftshift out from the current MSW produces a non-zero result. [0] is LSW.</remarks>
|
|
|
|
if (bits === undefined) {
|
|
bits = 1;
|
|
} else if (bits >= DIGIT_BITS || bits < 0) {
|
|
throw new Error("bit count must be smaller than DIGIT_BITS and positive in shiftLeft");
|
|
}
|
|
if (length === undefined) {
|
|
length = source.length;
|
|
}
|
|
|
|
var rightShiftBitCount = DIGIT_BITS - bits;
|
|
// The following line is correct. destination should remain undefined if there are no bits going into it.
|
|
destination[length] = (source[length - 1] >>> (DIGIT_BITS - bits)) || destination[length];
|
|
for (var i = length - 1; i > 0; i--) {
|
|
destination[i] = ((source[i] << bits) | ((source[i - 1] >>> rightShiftBitCount))) & DIGIT_MASK;
|
|
// a[i] = high|low = low bits of a[i] | high bits of a[i-1]
|
|
}
|
|
|
|
destination[0] = (source[0] << bits) & DIGIT_MASK;
|
|
}
|
|
|
|
//// //// //// //// //// //// //// //// //// //// //// //// //// /
|
|
// Low level math routines
|
|
//// //// //// //// //// //// //// //// //// //// //// //// //// /
|
|
|
|
function add(addend1, addend2, sum) {
|
|
/// <summary>Add two arrays of digits into a third array: sum = addend1 + addend2. Carry is recorded in the output if there is one.</summary>
|
|
/// <param name="addend1" type="Array">The first addend.</param>
|
|
/// <param name="addend2" type="Array">The second added.</param>
|
|
/// <param name="sum" type="Array">The output sum buffer addend1 + addend2.</param>
|
|
/// <returns type="Number" integer="true">If carry out then 1, otherwise 0.</returns>
|
|
|
|
// Determine which is shorter
|
|
var shortArray = addend1;
|
|
var longArray = addend2;
|
|
if (addend2.length < addend1.length) {
|
|
shortArray = addend2;
|
|
longArray = addend1;
|
|
}
|
|
|
|
// Perform the addition
|
|
var s = shortArray.length;
|
|
var carry = 0;
|
|
var i;
|
|
|
|
for (i = 0; i < s; i += 1) {
|
|
carry += shortArray[i] + longArray[i];
|
|
sum[i] = carry & DIGIT_MASK;
|
|
carry = (carry >> DIGIT_BITS);
|
|
}
|
|
|
|
for (i = s; i < longArray.length; i += 1) {
|
|
carry += longArray[i];
|
|
sum[i] = carry & DIGIT_MASK;
|
|
carry = (carry >> DIGIT_BITS);
|
|
}
|
|
|
|
// Set output length
|
|
sum.length = longArray.length;
|
|
|
|
// Is there a carry into the next digit?
|
|
if (carry !== 0) {
|
|
sum[i] = carry & DIGIT_MASK;
|
|
}
|
|
|
|
return carry;
|
|
}
|
|
|
|
function subtract(minuend, subtrahend, difference) {
|
|
/// <summary>Subtraction: difference = minuend - subtrahend. Condition: minuend.length <= subtrahend.length.</summary>
|
|
/// <param name="minuend" type="Array">Minuend.</param>
|
|
/// <param name="subtrahend" type="Array">Subtrahend.</param>
|
|
/// <param name="difference" type="Array">The difference.</param>
|
|
/// <returns type="Number" integer="true">Returns -1 if there is a borrow (minuend < subtrahend), or 0 if there isn't (minuend >= subtrahend).</returns>
|
|
|
|
var s = subtrahend.length;
|
|
if (minuend.length < subtrahend.length) {
|
|
s = mswIndex(subtrahend) + 1;
|
|
if (minuend.length < s) {
|
|
throw new Error("Subtrahend is longer than minuend, not supported.");
|
|
}
|
|
}
|
|
var i, carry = 0;
|
|
for (i = 0; i < s; i += 1) {
|
|
carry += minuend[i] - subtrahend[i];
|
|
difference[i] = carry & DIGIT_MASK;
|
|
carry = carry >> DIGIT_BITS;
|
|
}
|
|
|
|
// Propagate the carry by subtracting from minuend into difference
|
|
while (i < minuend.length) {
|
|
carry += minuend[i];
|
|
difference[i++] = carry & DIGIT_MASK;
|
|
carry = carry >> DIGIT_BITS;
|
|
}
|
|
|
|
return carry;
|
|
}
|
|
|
|
function multiply(multiplicant, multiplier, product) {
|
|
/// <summary>Multiply two arrays of digits into a third array using schoolbook.</summary>
|
|
/// <param name="multiplicant" type="Array">Multiplicand.</param>
|
|
/// <param name="multiplier">Multiplier.</param>
|
|
/// <param name="product" type="Array">Product = multiplicant * multiplier.</param>
|
|
/// <returns type="Array">The result buffer; same as the product argument.</returns>
|
|
|
|
// Single number or an array?
|
|
var mplr = (typeof multiplier === "number") ? [multiplier] : multiplier;
|
|
var s = Math.max(multiplicant.length, mplr.length);
|
|
var i, j, u;
|
|
|
|
// P <- 0
|
|
// We only have to do this for half of result
|
|
// since the upper half is over-written on the first i iteration.
|
|
for (i = 0; i < s; i += 1) {
|
|
product[i] = 0;
|
|
}
|
|
|
|
// For i from 0 by 1 to s - 1 do
|
|
for (i = 0; i < mplr.length; i += 1) {
|
|
|
|
// 'u <- 0'
|
|
u = 0;
|
|
|
|
// For j from 0 by 1 to s - 1 do
|
|
for (j = 0; j < multiplicant.length; j += 1) {
|
|
|
|
// '(u,v) <- a_j * b_i + p_(i+j) + u'
|
|
u += multiplicant[j] * mplr[i] + product[i + j];
|
|
|
|
// 'p_(i+j) <- v'
|
|
product[i + j] = (u & DIGIT_MASK) /* v */;
|
|
u = Math.floor(u / DIGIT_BASE); // 'v <- u, u <- 0'
|
|
}
|
|
|
|
product[multiplicant.length + i] = (u & DIGIT_MASK);
|
|
}
|
|
|
|
// set product length; there may still be leading zero digits after this
|
|
product.length = multiplicant.length + mplr.length;
|
|
|
|
return product;
|
|
}
|
|
|
|
function divRem(dividend, divisor, quotient, remainder, temp1, temp2) {
|
|
/// <summary>Computes the quotient q and remainder r when dividend is divided by
|
|
/// divisor.</summary>
|
|
/// <param name="dividend" type="Array">The dividend.</param>
|
|
/// <param name="divisor" type="Array">The divisor.</param>
|
|
/// <param name="quotient" type="Array">Receives the quotient (n digits).</param>
|
|
/// <param name="remainder" type="Array">Receives the remainder (n digits).</param>
|
|
/// <param name="temp1" type="Array" optional="true">Temporary storage (n digits).</param>
|
|
/// <param name="temp2" type="Array" optional="true">Temporary storage (n digits).</param>
|
|
/// <remarks>This is an implementation of Figure 9-1 is Knuth's Algorithm D [Knu2 sec. 4.3.1].
|
|
/// Throws error on division by zero.
|
|
/// </remarks>
|
|
var m = mswIndex(dividend) + 1; // zero-based length
|
|
var n = mswIndex(divisor) + 1; // zero-based length
|
|
var qhat, rhat, carry, p, t, i, j;
|
|
|
|
// Check for quick results and clear out conditionals
|
|
if (m < n) {
|
|
// dividend < divisor. q=0, remainder=dividend
|
|
copyArray(dividend, 0, remainder, 0, dividend.length);
|
|
remainder.length = dividend.length;
|
|
normalizeDigitArray(remainder);
|
|
quotient[0] = 0;
|
|
quotient.length = 1;
|
|
return;
|
|
} else if (n === 0 || (n === 1 && divisor[n - 1] === 0)) { // self-explanatory
|
|
throw new Error("Division by zero.");
|
|
} else if (n === 1) {
|
|
// divisor is single digit; do a simpler division
|
|
t = divisor[0];
|
|
rhat = 0;
|
|
for (j = m - 1; j >= 0; j--) {
|
|
p = (rhat * DIGIT_BASE) + dividend[j];
|
|
quotient[j] = (p / t) & DIGIT_MASK;
|
|
rhat = (p - quotient[j] * t) & DIGIT_MASK;
|
|
}
|
|
quotient.length = m;
|
|
normalizeDigitArray(quotient);
|
|
remainder[0] = rhat;
|
|
remainder.length = 1;
|
|
return;
|
|
}
|
|
|
|
// Normalization step. Align dividend and divisor so that their
|
|
// most significant digits are at the same index.
|
|
// Shift divisor by so many bits (0..DIGIT_BITS-1) to make MSB non-zero.
|
|
var s = DIGIT_BITS - 1 - bitScanForward(divisor[n - 1]);
|
|
var vn = temp1 || [];
|
|
vn.length = n;
|
|
shiftLeft(divisor, vn, s, n);
|
|
|
|
var un = temp2 || [];
|
|
un.length = m;
|
|
shiftLeft(dividend, un, s, m);
|
|
un[m] = un[m] || 0; // must not be undefined
|
|
|
|
// Main division loop with quotient estimate qhat
|
|
quotient.length = m - n + 1;
|
|
remainder.length = n;
|
|
for (j = m - n; j >= 0; j--) {
|
|
// Estimate quotient qhat using two-digit by one-digit division
|
|
// because 3-digit by 2-digit division is more complex. Then, correct qhat after this.
|
|
qhat = Math.floor((un[j + n] * DIGIT_BASE + un[j + n - 1]) / vn[n - 1]);
|
|
rhat = (un[j + n] * DIGIT_BASE + un[j + n - 1]) - qhat * vn[n - 1];
|
|
|
|
// If the quotient estimate is large, reduce the quotient estimate till the following is satisfied:
|
|
// qhat = {un[j+n, j+n-1, j+n-2]} div {uv[n-1,n-2]}
|
|
while (true) {
|
|
if (qhat >= DIGIT_BASE || (qhat * vn[n - 2]) > ((rhat * DIGIT_BASE) + un[j + n - 2])) {
|
|
qhat = qhat - 1;
|
|
rhat = rhat + vn[n - 1];
|
|
if (rhat < DIGIT_BASE) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Multiply the [shifted] divisor by the quotient estimate and subtract the product from the dividend
|
|
// un = un - qhat*vn
|
|
carry = 0;
|
|
for (i = 0; i < n; i++) {
|
|
p = qhat * vn[i];
|
|
t = un[i + j] - carry - (p & DIGIT_MASK);
|
|
un[i + j] = t & DIGIT_MASK;
|
|
//carry = (p >>> DIGIT_BITS) - (t >> DIGIT_BITS);
|
|
// Don't shift: integer shifts are defined over 32-bit numbers in JS.
|
|
carry = Math.floor(p / DIGIT_BASE) - Math.floor(t / DIGIT_BASE);
|
|
}
|
|
|
|
t = un[j + n] - carry;
|
|
un[j + n] = t & DIGIT_MASK;
|
|
|
|
// Store the estimated quotient digit (may need correction)
|
|
quotient[j] = qhat & DIGIT_MASK;
|
|
|
|
// Correction needed?
|
|
if (t < 0) {
|
|
// quotient too big (at most by 1 divisor)
|
|
// decrement the quotient, and add [shifted] divisor back to the running dividend (remainder)
|
|
quotient[j] = quotient[j] - 1;
|
|
|
|
// un = un + vn
|
|
carry = 0;
|
|
for (i = 0; i < n; i++) {
|
|
t = un[i + j] + vn[i] + carry;
|
|
un[i + j] = t & DIGIT_MASK;
|
|
carry = t >> DIGIT_BITS;
|
|
}
|
|
un[j + n] = (un[j + n] + carry) & DIGIT_MASK;
|
|
}
|
|
}
|
|
|
|
// De-normalize the remainder (shift right by s bits).
|
|
for (i = 0; i < n; i++) {
|
|
remainder[i] = ((un[i] >>> s) | (un[i + 1] << (DIGIT_BITS - s))) & DIGIT_MASK;
|
|
}
|
|
|
|
// Compute correct lengths for the quotient and remainder
|
|
normalizeDigitArray(quotient);
|
|
normalizeDigitArray(remainder);
|
|
}
|
|
|
|
function reduce(number, modulus, remainder, temp1, temp2) {
|
|
/// <summary>Integer reduction by a modulus to compute number mod modulus. This function uses division,
|
|
/// and should not be used for repetitive operations.</summary>
|
|
/// <param name="number" type="Array">Input number to reduce.</param>
|
|
/// <param name="modulus" type="Array">Modulus to reduce the input by.</param>
|
|
/// <param name="remainder" type="Array">Output remainder = number mod modulus.</param>
|
|
/// <param name="temp1" type="Array" optional="true">Temporary space, optional.</param>
|
|
/// <param name="temp2" type="Array" optional="true">Temporary space, optional.</param>
|
|
/// <returns type="Array">The resulting remainder is in 0..modulus-1; same as "remainder".</returns>
|
|
|
|
// TODO: More efficient reduction implementation
|
|
var quotient = [];
|
|
divRem(number, modulus, quotient, remainder, temp1, temp2);
|
|
|
|
return remainder;
|
|
}
|
|
|
|
function modMul(multiplicant, multiplier, modulus, product, temp1, temp2) {
|
|
/// <summary>Moduler multiplication of two numbers for a modulus. This function uses multiply and divide method,
|
|
/// and should not be used for repetitive operations.
|
|
/// product can be same as multiplicant and multiplier.</summary>
|
|
/// <param name="multiplicant" type="Array">Multiplicand.</param>
|
|
/// <param name="multiplier">Multiplier.</param>
|
|
/// <param name="modulus" type="Array">Modulus to reduce the product.</param>
|
|
/// <param name="product" type="Array">Output product = multiplicant * multiplier mod modulus.</param>
|
|
/// <param name="temp1" type="Array" optional="true">Scratch space (optional).</param>
|
|
/// <param name="temp2" type="Array" optional="true">Scratch space (optional).</param>
|
|
/// <returns type="Array">The resulting product in in 0..modulus-1; same as product.</returns>
|
|
|
|
var quotient = [];
|
|
multiply(multiplicant, multiplier, quotient);
|
|
divRem(quotient, modulus, quotient, product, temp1, temp2);
|
|
|
|
return product;
|
|
}
|
|
|
|
function eea(a, b, upp, vpp, rpp) {
|
|
/// <summary>Extended Euclidean Algorithm, Berlekamp's version. On return
|
|
/// b*upp - a*vpp = (-1)(k-1)*rpp.</summary>
|
|
/// <param name="a" type="Array">The first number a.</param>
|
|
/// <param name="b" type="Array">The second number b.</param>
|
|
/// <param name="upp" type="Array">a^-1 mod b if gcd=1. Optional.</param>
|
|
/// <param name="vpp" type="Array">b^-1 mod a if gcd=1. Optional./</param>
|
|
/// <param name="rpp" type="Array">gcd(a,b).</param>
|
|
/// <returns type="Number">k value.</returns>
|
|
/// <remarks>Algebraic Coding Theory, Pages 24-30.<code>
|
|
/// if k is odd
|
|
/// a*a^-1 = 1 mod b ---> a^-1 = b - vpp
|
|
/// b*b^-1 = 1 mod a ---> b^-1 = vpp
|
|
/// if k is even
|
|
/// a*a^-1 = 1 mod b ---> a^-1 = upp
|
|
/// b*b^-1 = 1 mod a ---> b^-1 = a - upp
|
|
/// </code></remarks>
|
|
// Initialize rpp and rp from two inputs a and b s.t. rpp >= rp
|
|
var rp; // initialized from a or b
|
|
if (isZero(a)) { // gcd = (0,b) = b
|
|
copyArray(b, 0, rpp, 0, b.length);
|
|
rpp.length = b.length;
|
|
return 0;
|
|
} else if (isZero(b)) { // gcd = (a,0) = a
|
|
copyArray(a, 0, rpp, 0, a.length);
|
|
rpp.length = a.length;
|
|
return 0;
|
|
} else if (compareDigits(a, b) < 0) {
|
|
rp = a.slice(0);
|
|
copyArray(b, 0, rpp, 0, b.length); rpp.length = b.length;
|
|
} else {
|
|
rp = b.slice(0);
|
|
copyArray(a, 0, rpp, 0, a.length); rpp.length = a.length;
|
|
}
|
|
|
|
normalizeDigitArray(rpp);
|
|
normalizeDigitArray(rp);
|
|
var q = new Array(rpp.length);
|
|
var r = new Array(rpp.length);
|
|
|
|
var v = new Array(rpp.length);
|
|
var vppPresent = vpp !== undefined;
|
|
var vp;
|
|
if (vppPresent) {
|
|
vp = new Array(rpp.length);
|
|
vp[0] = 1; vp.length = 1;
|
|
vpp[0] = 0; vpp.length = 1;
|
|
}
|
|
|
|
var up;
|
|
var u = new Array(rpp.length);
|
|
var uppPresent = upp !== undefined;
|
|
if (uppPresent) {
|
|
up = new Array(rpp.length);
|
|
up[0] = 0; up.length = 1;
|
|
upp[0] = 1; upp.length = 1;
|
|
}
|
|
|
|
// k starts at -1 so that on return, it is >=0.
|
|
// In the following discussion, assume a<b and this is computing a^-1 mod b where (a,b)=1, a<b.
|
|
// Initialize rp=a, rpp=b.
|
|
// The integer k keeps track of the sign of a^-1 (0 = positive) in b = q*a + r with 0 = q*a + r mod b
|
|
// such that for q=a^-1 and r=1 (which is gcd=1 for inverse to exist), we have q*a = (-1)^k mod b.
|
|
// Thus, for odd k, q*a = -1 mod b, and a^-1 = b-q as in the description.
|
|
var k = -1;
|
|
|
|
// At the end, gcd = rp = (a,b)
|
|
var upp_out = upp;
|
|
var vpp_out = vpp;
|
|
var rpp_out = rpp;
|
|
var save;
|
|
|
|
// Recycle u and v as temp variables in division (divRem).
|
|
while (!isZero(rp)) {
|
|
// rpp = q*rp + r: compute q, r
|
|
divRem(rpp, rp, q, r, u, v);
|
|
|
|
if (uppPresent) {
|
|
// u = q*up + upp
|
|
// upp=up, up=u, u=upp
|
|
multiply(q, up, u);
|
|
add(u, upp, u);
|
|
normalizeDigitArray(u);
|
|
save = upp;
|
|
upp = up;
|
|
up = u;
|
|
u = save;
|
|
}
|
|
|
|
if (vppPresent) {
|
|
// v = q*vp + vpp
|
|
// vpp=vp, vp=v, v=vpp
|
|
multiply(q, vp, v);
|
|
add(v, vpp, v);
|
|
normalizeDigitArray(v);
|
|
save = vpp;
|
|
vpp = vp;
|
|
vp = v;
|
|
v = save;
|
|
}
|
|
|
|
// rpp=rp, rp=r, r=rpp
|
|
save = rpp;
|
|
rpp = rp;
|
|
rp = r;
|
|
r = save;
|
|
|
|
k++;
|
|
}
|
|
|
|
// copy to output upp, vpp, rpp
|
|
if (uppPresent) {
|
|
copyArray(upp, 0, upp_out, 0, upp.length); upp_out.length = upp.length;
|
|
}
|
|
if (vppPresent) {
|
|
copyArray(vpp, 0, vpp_out, 0, vpp.length); vpp_out.length = vpp.length;
|
|
}
|
|
copyArray(rpp, 0, rpp_out, 0, rpp.length); rpp_out.length = rpp.length;
|
|
|
|
return k;
|
|
}
|
|
|
|
function gcd(a, b, output) {
|
|
/// <summary>Compute greatest common divisor or a and b.</summary>
|
|
/// <param name="a" type="Array">First integer input.</param>
|
|
/// <param name="b" type="Array">Second integer input.</param>
|
|
/// <param name="output" type="Array" optional="true">GCD output (optional).</param>
|
|
/// <returns type="Array">GCD(a,b), the same object as the output parameter if given or a new object otherwise.</returns>
|
|
var aa = a;
|
|
var bb = b;
|
|
if (compareDigits(a, b) > 0) {
|
|
aa = b;
|
|
bb = a;
|
|
}
|
|
|
|
eea(aa, bb, undefined, undefined, output);
|
|
return normalizeDigitArray(output);
|
|
}
|
|
|
|
function modInv(a, n, aInv, pad) {
|
|
/// <summary>Modular multiplicative inverse a^-1 mod n.</summary>
|
|
/// <param name="a" type="Array">The number to invert. Condition: a < n, or the result would be n^-1 mod a.</param>
|
|
/// <param name="n" type="Array">The modulus.</param>
|
|
/// <param name="aInv" type="Array" optional="true">a^-1 mod n (optional).</param>
|
|
/// <param name="pad" type="Boolean" optional="true">True to pad the returned value to the length of the modulus (optional).</param>
|
|
/// <returns type="Array">a^-1 mod n. Same as the aInv parameter if the parameter is specified.</returns>
|
|
//var gcd = eea(a, n, inv);
|
|
var upp = new Array(n.length);
|
|
var vpp = new Array(n.length);
|
|
var rpp = new Array(n.length);
|
|
var k = eea(a, n, vpp, upp, rpp);
|
|
|
|
aInv = aInv || [];
|
|
if (compareDigits(rpp, One) !== 0) {
|
|
aInv[0] = NaN;
|
|
aInv.length = 1;
|
|
} else {
|
|
// gcd = 1, there is an inverse.
|
|
// Compute inverse from Berlekamp's EEA outputs.
|
|
if ((k & 1) === 1) {
|
|
subtract(n, upp, aInv);
|
|
} else {
|
|
copyArray(upp, 0, aInv, 0, upp.length); aInv.length = upp.length;
|
|
}
|
|
if (pad) {
|
|
normalizeDigitArray(aInv, n.length, true);
|
|
} else {
|
|
normalizeDigitArray(aInv);
|
|
}
|
|
}
|
|
|
|
return aInv;
|
|
}
|
|
|
|
function modExp(base, exponent, modulus, result) {
|
|
/// <summary>Modular exponentiation in an integer group.</summary>
|
|
/// <param name="base" type="Array">The base of the exponentiation.</param>
|
|
/// <param name="exponent" type="Array">The exponent.</param>
|
|
/// <param name="modulus" type="Array">Modulus to reduce the result.</param>
|
|
/// <param name="result" type="Array" optional="true">Output element that takes the modular exponentiation result (optional).</param>
|
|
/// <returns type="Array">Modular exponentiation result, same as <paramref name="result"/> if not null, or a new object.</returns>
|
|
|
|
result = result || [];
|
|
|
|
// If exponent is 0 return 1
|
|
if (compareDigits(exponent, Zero) === 0) {
|
|
result[0] = 1;
|
|
} else if (compareDigits(exponent, One) === 0) {
|
|
// If exponent is 1 return valueElement
|
|
copyArray(base, 0, result, 0, base.length);
|
|
result.length = base.length;
|
|
} else {
|
|
var montmul = new MontgomeryMultiplier(modulus);
|
|
normalizeDigitArray(base, montmul.s, true);
|
|
montmul.modExp(
|
|
base,
|
|
exponent,
|
|
result);
|
|
result.length = modulus.length;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function MontgomeryMultiplier(modulus) {
|
|
/// <summary>Construct a new montgomeryMultiplier object with the given modulus.</summary>
|
|
/// <param name="modulus" type="Array">A prime modulus in little-endian digit form</param>
|
|
/// <remarks>Montgomery Multiplier class
|
|
/// This class implements high performance montgomery multiplication using
|
|
/// CIOS, as well as modular exponentiation.</remarks>
|
|
|
|
function computeM0Prime(m0) {
|
|
/// <summary>Compute m' = -(m^-1) mod b, 16 bit digits. Based on Tolga Acar's code.</summary>
|
|
/// <param name="m0" type="Number" integer="true">Digit m.</param>
|
|
/// <returns type="Number">Digit m'.</returns>
|
|
var m0Pr = 1;
|
|
var a = 2;
|
|
var b = 3;
|
|
var c = b & m0;
|
|
|
|
for (var i = 2; i <= DIGIT_BITS; i += 1) {
|
|
if (a < c) {
|
|
m0Pr += a;
|
|
}
|
|
|
|
a = a << 1;
|
|
b = (b << 1) | 1;
|
|
c = m0 * m0Pr & b;
|
|
}
|
|
|
|
var result = (~m0Pr & DIGIT_MASK) + 1;
|
|
return result;
|
|
}
|
|
|
|
function montgomeryMultiply(multiplicant, multiplier, result, ctx) {
|
|
/// <summary>Montgomery multiplication with the CIOS method.</summary>
|
|
/// <param name="multiplicant" type="Array">Multiplicant.</param>
|
|
/// <param name="multiplier" type="Array">Multiplier.</param>
|
|
/// <param name="result" type="Array">Computed result multiplicant * multiplier * r^-1 mod n.</param>
|
|
/// <param name="ctx" type="MontgomeryMultiplier" optional="true">Context (optional = this).</param>
|
|
|
|
ctx = ctx || this;
|
|
|
|
// Upper digits of result
|
|
var resultHigh = 0;
|
|
|
|
// Precompute offsets
|
|
var s = ctx.m.length;
|
|
var sMinus1 = s - 1;
|
|
|
|
// Local cache of m0, m', digitmask, digitbits
|
|
var mPrime = ctx.mPrime;
|
|
var m0 = ctx.m0;
|
|
var left0 = multiplicant[0];
|
|
|
|
var uv = 0, rightI, q, i, j, k;
|
|
|
|
// Clear the result array
|
|
for (i = 0; i < s; i += 1) {
|
|
result[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < s; i += 1) {
|
|
|
|
rightI = multiplier[i]; // Cache array value
|
|
|
|
// 'u <- 0'
|
|
|
|
// ---- UNROLL FIRST ITERATION (j == 0) ----
|
|
uv = left0 * rightI + result[0];
|
|
result[0] = uv & DIGIT_MASK;
|
|
uv = Math.floor(uv / DIGIT_BASE);
|
|
|
|
// ---- REMAINING ITERATIONS ----
|
|
for (j = 1; j < s; j += 1) {
|
|
|
|
// '(u,v) <- a_j * b_i + z_j + u'
|
|
// uv = uv >>> DIGIT_BITS;
|
|
// Don't shift: JS supports shifts over 32-bit integers, only.
|
|
uv = multiplicant[j] * rightI + result[j] + uv;
|
|
result[j] = uv & DIGIT_MASK;
|
|
uv = Math.floor(uv / DIGIT_BASE);
|
|
}
|
|
// -------------------------------
|
|
|
|
// '(u,v) <- z_s + u'.
|
|
// 'z_s <- v'.
|
|
// 'z_s+1 <- u'.
|
|
resultHigh = resultHigh + uv;
|
|
|
|
// 'q <- z_0 * m' mod digitBase
|
|
q = (result[0] * mPrime) & DIGIT_MASK;
|
|
|
|
// '(u,v) <- z_0 + m_0 * q'
|
|
uv = Math.floor((result[0] + (m0 * q)) / DIGIT_BASE);
|
|
|
|
// For j from 1 by 1 to s-1
|
|
for (j = 1, k = 0; j < s; j += 1, k++) {
|
|
// '(u,v) <- m_j * q + z_j + u'
|
|
uv = ctx.m[j] * q + result[j] + uv;
|
|
|
|
// 'z_j-1 <- v'
|
|
result[k] = uv & DIGIT_MASK;
|
|
uv = Math.floor(uv / DIGIT_BASE);
|
|
}
|
|
|
|
// '(u,v) <- z_s + u'.
|
|
// 'z_s-1 <- v'.
|
|
// 'z_s <- z_s+1 + u'.
|
|
resultHigh += uv;
|
|
result[sMinus1] = resultHigh & DIGIT_MASK;
|
|
resultHigh = Math.floor(resultHigh / DIGIT_BASE);
|
|
}
|
|
|
|
// Subtract modulus
|
|
|
|
// Make sure temp1 isn't also our passed-in result array.
|
|
// This can happen if temp1 was returned as a result to a previous call.
|
|
var resultMinusM = ctx.temp1 === result ? ctx.temp2 : ctx.temp1,
|
|
carry = 0;
|
|
|
|
for (i = 0; i < s; i += 1) {
|
|
carry = result[i] - ctx.m[i] + (carry >> DIGIT_BITS);
|
|
resultMinusM[i] = carry & DIGIT_MASK;
|
|
}
|
|
carry = (resultHigh & DIGIT_MASK) + (carry >> DIGIT_BITS);
|
|
carry = (resultHigh >>> DIGIT_BITS) + (carry >> DIGIT_BITS);
|
|
|
|
// Use carry as a mask to copy result into the return array
|
|
for (i = 0; i < s; i += 1) {
|
|
result[i] = (carry & (resultMinusM[i] ^ result[i])) ^ resultMinusM[i];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
function convertToMontgomeryForm( digits) {
|
|
/// <summary>Convert the digits in standard form to Montgomery residue representation.</summary>
|
|
/// <param name="digits" type="Array">Input digits to convert, and also the output converted digits.</param>
|
|
|
|
// Pad missing digits with zeroes
|
|
if (digits.length < this.s) {
|
|
digits.length = this.s;
|
|
for (var i = 0; i < this.s; i++) {
|
|
digits[i] = isNaN(digits[i]) ? 0 : digits[i];
|
|
}
|
|
}
|
|
|
|
var result = createArray(digits.length);
|
|
|
|
this.montgomeryMultiply(digits, this.rSquaredModm, result);
|
|
for (i = 0; i < this.s; i += 1) {
|
|
digits[i] = result[i];
|
|
}
|
|
}
|
|
|
|
function convertToStandardForm(digits) {
|
|
/// <summary>Convert from Montgomery residue representation to the standard form.</summary>
|
|
/// <param name="digits" type="Array">Input digits to convert, and also the output converted digits.</param>
|
|
this.montgomeryMultiply(digits, this.one, this.temp1);
|
|
for (var i = 0; i < this.s; i += 1) {
|
|
digits[i] = this.temp1[i];
|
|
}
|
|
}
|
|
|
|
function modExp(base, exponent, result) {
|
|
/// <summary>Compute base to exponent mod m into result.</summary>
|
|
/// <param name="base" type="Array">Base of length s in the context.</param>
|
|
/// <param name="exponent" type="Array">Exponent.</param>
|
|
/// <param name="result" type="Array">Output as base raised to exponent, and reduced to the modulus in the context.</param>
|
|
/// <returns type="Array">result base^exponent mod m; the same result object.</returns>
|
|
|
|
// Skip leading zero bits in the exponent
|
|
// The total number of bits to scan in the exponent must be an integral multiple of
|
|
// the number of bits to use in the exponent.
|
|
var i;
|
|
var expBitsToScan = 2; // scan 2 bits at a time
|
|
var expMask = DIGIT_MASK >>> (DIGIT_BITS - expBitsToScan);
|
|
for (i = exponent.length - 1; i > 0 && exponent[i] === 0; i--) {
|
|
}
|
|
var bitsToScan = i * DIGIT_BITS + bitScanForward(exponent[i]) + 1;
|
|
bitsToScan = bitsToScan + (expBitsToScan - (bitsToScan % expBitsToScan));
|
|
var shiftAmt = (bitsToScan % DIGIT_BITS) - expBitsToScan;
|
|
if (shiftAmt < 0) {
|
|
shiftAmt += DIGIT_BITS;
|
|
}
|
|
var mask = expMask << shiftAmt;
|
|
|
|
// Prepare the precomputation table of base for k bits
|
|
// base[0..3] = [r, r*base, r*base^2, r*base^3] mod m
|
|
for (i = 1; i < baseTable.length; i++) {
|
|
modMul(baseTable[i - 1], base, this.m, baseTable[i], temp1, temp2);
|
|
normalizeDigitArray(baseTable[i], this.s, true);
|
|
}
|
|
|
|
// a is the running result: a = 1*r mod m
|
|
// TODO: Skip the first loop iteration below to avoid 1*1 mod m (minor optimization)
|
|
var fourthPower = new Array(this.s);
|
|
var squared = result;
|
|
var partialResult = temp2;
|
|
copyArray(this.rModM, 0, partialResult, 0, this.s);
|
|
|
|
// Scan the exponent expBitsToScan bits at a time
|
|
var tableIndex;
|
|
while (bitsToScan > 0) {
|
|
// result <- Mont(a, a);
|
|
// result <- Mont(result, a);
|
|
this.montgomeryMultiply(partialResult, partialResult, squared);
|
|
this.montgomeryMultiply(squared, squared, fourthPower);
|
|
|
|
// tableIndex <- the current bits of the scanned exponent
|
|
tableIndex = (exponent[Math.floor((bitsToScan - 1) / DIGIT_BITS)] & mask) >>> shiftAmt;
|
|
|
|
// aDigits = result * table[tableIndex]
|
|
this.montgomeryMultiply(fourthPower, baseTable[tableIndex], partialResult);
|
|
|
|
bitsToScan = bitsToScan - expBitsToScan;
|
|
shiftAmt = shiftAmt - expBitsToScan;
|
|
mask = mask >>> expBitsToScan;
|
|
if (mask === 0) {
|
|
mask = expMask << (DIGIT_BITS - expBitsToScan);
|
|
shiftAmt = DIGIT_BITS - expBitsToScan;
|
|
}
|
|
}
|
|
|
|
// result = Mont(a, 1)
|
|
this.montgomeryMultiply(partialResult, this.one, result);
|
|
return result;
|
|
}
|
|
|
|
// Modulus
|
|
var m = modulus;
|
|
|
|
// First digit of modulus
|
|
var m0 = m[0];
|
|
|
|
// Operand size (number of digits)
|
|
var s = m.length;
|
|
|
|
// The number one - used by modpow
|
|
var one = createArray(s);
|
|
one[0] = 1;
|
|
|
|
// Compute m' = -(m^-1) mod b used by CIOS
|
|
var mPrime = computeM0Prime(m0);
|
|
|
|
// Create r and compute r mod m
|
|
// Since we are base b integers of length s, we want
|
|
// 'r = b^n = b^s'.
|
|
var quotient = createArray(2 * s + 1);
|
|
var rRemainder = createArray(s + 1); // becomes rModM
|
|
var temp1 = createArray(2 * s + 1);
|
|
var temp2 = createArray(2 * s + 1);
|
|
var rDigits = rRemainder;
|
|
rDigits[s] = 1;
|
|
divRem(
|
|
rDigits,
|
|
m,
|
|
quotient,
|
|
rRemainder,
|
|
temp1,
|
|
temp2);
|
|
var rModM = normalizeDigitArray(rRemainder, s, true);
|
|
|
|
// Compute R^2 mod m
|
|
var rSquaredModm = createArray(2 * s + 1);
|
|
var rSquaredDigits = rSquaredModm;
|
|
rSquaredDigits[s * 2] = 1;
|
|
divRem(
|
|
rSquaredDigits,
|
|
m,
|
|
quotient,
|
|
rSquaredModm,
|
|
temp1,
|
|
temp2);
|
|
normalizeDigitArray(rSquaredModm, s, true);
|
|
|
|
// Ready to do MontMul now - compute R^3
|
|
var rCubedModm = createArray(s);
|
|
var ctx = {
|
|
m: m, mPrime: mPrime, m0: m0, temp1: temp1, temp2: temp2
|
|
};
|
|
montgomeryMultiply(
|
|
rSquaredModm,
|
|
rSquaredModm,
|
|
rCubedModm,
|
|
ctx);
|
|
|
|
// Allocate space for multi-bit modular exponentiation
|
|
var baseTable = new Array(4);
|
|
baseTable[0] = rModM;
|
|
baseTable[1] = new Array(s);
|
|
baseTable[2] = new Array(s);
|
|
baseTable[3] = new Array(s);
|
|
|
|
// Return a per-instance context for Montgomery multiplier.
|
|
// There is no need to use the "new" keyword when using this function.
|
|
return {
|
|
// Modulus
|
|
m: modulus,
|
|
|
|
// First digit of modulus
|
|
m0: m0,
|
|
|
|
// Compute m' = -(m^-1) mod b used by CIOS
|
|
mPrime: mPrime,
|
|
|
|
rSquaredModm: rSquaredModm,
|
|
s: s,
|
|
rModM: rModM,
|
|
rCubedModm: rCubedModm,
|
|
one: one,
|
|
temp1: temp1,
|
|
temp2: temp2,
|
|
|
|
// Functions
|
|
convertToMontgomeryForm: convertToMontgomeryForm,
|
|
convertToStandardForm: convertToStandardForm,
|
|
montgomeryMultiply: montgomeryMultiply,
|
|
modExp: modExp
|
|
};
|
|
}
|
|
|
|
function IntegerGroup(modulusBytes) {
|
|
/// <summary>Construct a new IntegerGroup object with the given modulus.</summary>
|
|
/// <param name="modulusBytes" type="Array">A big-endian number to represent the modulus in a byte array.</param>
|
|
/// <remarks>This class represents the set of integers mod n. It is meant to be used in
|
|
/// a variety of situations, for example to perform operations in the additive
|
|
/// or multiplicative groups mod n. The modulus can be an arbitrary integer and
|
|
/// in the case that it is a prime p then the integer group is the field Fp. The
|
|
/// user should be aware of what type of object the given modulus produces, and
|
|
/// thus which operations are valid.</remarks>
|
|
|
|
// Modulus
|
|
var m_modulus = bytesToDigits(modulusBytes);
|
|
|
|
// Length of an element in digits
|
|
var m_digitWidth = m_modulus.length;
|
|
|
|
// Setup numeric constants
|
|
var m_zero = intToDigits(0, m_digitWidth);
|
|
var m_one = intToDigits(1, m_digitWidth);
|
|
|
|
// Temp storage.
|
|
// Allocation in js is very slow, we use these temp arrays to avoid it.
|
|
var temp0 = createArray(m_digitWidth);
|
|
var temp1 = createArray(m_digitWidth);
|
|
|
|
// Create montgomery multiplier object
|
|
var montmul = new MontgomeryMultiplier(m_modulus);
|
|
|
|
function createElementFromBytes(bytes) {
|
|
/// <summary>Create a new element object from a byte value.</summary>
|
|
/// <param name="bytes" type="Array">Desired element in big-endian format in an array of bytes.</param>
|
|
/// <returns type="integerGroupElement">An element object representing the given element.</returns>
|
|
var digits = bytesToDigits(bytes);
|
|
|
|
// Check size of the new element
|
|
if (cryptoMath.compareDigits(digits, this.m_modulus) >= 0) {
|
|
// Too many digits
|
|
throw new Error("The number provided is not an element of this group");
|
|
}
|
|
|
|
// expand to the group modulus length
|
|
normalizeDigitArray(digits, this.m_digitWidth, true);
|
|
return integerGroupElement(digits, this);
|
|
}
|
|
|
|
function createElementFromInteger(integer) {
|
|
/// <summary>Create a new element object from an integer value.</summary>
|
|
/// <param name="integer" type="Number" integer="true">Desired element as an integer.</param>
|
|
/// <returns type="integerGroupElement">An element object representing the given element.</returns>
|
|
var digits = intToDigits(integer, this.m_digitWidth);
|
|
return integerGroupElement(digits, this);
|
|
}
|
|
|
|
function createElementFromDigits(digits) {
|
|
/// <summary>Create a new element object from a digit array.</summary>
|
|
/// <param name="digits" type="Array">Desired element as a digit array.</param>
|
|
/// <returns type="integerGroupElement">Object initialized with the given value.</returns>
|
|
cryptoMath.normalizeDigitArray(digits, this.m_digitWidth, true);
|
|
return integerGroupElement(digits, this);
|
|
}
|
|
|
|
function equals(otherGroup) {
|
|
/// <summary>Return true if the given object is equivalent to this one.</summary>
|
|
/// <param name="otherGroup" type="IntegerGroup"/>)
|
|
/// <returns type="Boolean">True if the given objects are equivalent.</returns>
|
|
|
|
return compareDigits(this.m_modulus, otherGroup.m_modulus) === 0;
|
|
}
|
|
|
|
function add(addend1, addend2, sum) {
|
|
/// <summary>Add this element to another element.</summary>
|
|
/// <param name="addend1" type="integerGroupElement"/>
|
|
/// <param name="addend2" type="integerGroupElement"/>
|
|
/// <param name="sum" type="integerGroupElement"/>
|
|
|
|
var i;
|
|
var s = this.m_digitWidth;
|
|
var result = sum.m_digits;
|
|
cryptoMath.add(addend1.m_digits, addend2.m_digits, result);
|
|
var mask = compareDigits(result, this.m_modulus) >= 0 ? DIGIT_MASK : 0;
|
|
|
|
// Conditional reduction by the modulus (one subtraction, only) only if the sum>modulus in almost constant time.
|
|
// The result is unmodified if the computed sum < modulus already.
|
|
var carry = 0;
|
|
for (i = 0; i < s; i += 1) {
|
|
carry = result[i] - (this.m_modulus[i] & mask) + carry;
|
|
result[i] = carry & DIGIT_MASK;
|
|
carry = (carry >> DIGIT_BITS);
|
|
}
|
|
|
|
result.length = s;
|
|
}
|
|
|
|
function subtract(leftElement, rightElement, outputElement) {
|
|
/// <summary>Subtract an element from another element.</summary>
|
|
/// <param name="leftElement" type="integerGroupElement"/>
|
|
/// <param name="rightElement" type="integerGroupElement"/>
|
|
/// <param name="outputElement" type="integerGroupElement"/>
|
|
|
|
var i, s = this.m_digitWidth;
|
|
var result = outputElement.m_digits;
|
|
var carry = cryptoMath.subtract(leftElement.m_digits, rightElement.m_digits, outputElement.m_digits);
|
|
|
|
// Final borrow?
|
|
if (carry === -1) {
|
|
carry = 0;
|
|
for (i = 0; i < s; i += 1) {
|
|
carry += result[i] + this.m_modulus[i];
|
|
result[i] = carry & DIGIT_MASK;
|
|
carry = carry >> DIGIT_BITS;
|
|
}
|
|
}
|
|
}
|
|
|
|
function inverse(element, outputElement) {
|
|
/// <summary>Compute the modular inverse of the given element.</summary>
|
|
/// <param name="element" type="integerGroupElement">The element to be inverted.</param>
|
|
/// <param name="outputElement" type="integerGroupElement">Receives the inverse element.</param>
|
|
cryptoMath.modInv(element.m_digits, this.m_modulus, outputElement.m_digits);
|
|
}
|
|
|
|
function multiply(multiplicant, multiplier, product) {
|
|
/// <summary>Multiply an element by another element in the integer group.</summary>
|
|
/// <param name="multiplicant" type="integerGroupElement">Multiplicand.</param>
|
|
/// <param name="multiplier" type="integerGroupElement">Multiplier.</param>
|
|
/// <param name="product" type="integerGroupElement">Product reduced by the group modulus.</param>
|
|
/// <returns type="Array">Same as <paramref name="product"/>.</returns>
|
|
|
|
return cryptoMath.modMul(multiplicant.m_digits, multiplier.m_digits, this.m_modulus, product.m_digits, temp0, temp1);
|
|
}
|
|
|
|
function modexp(valueElement, exponent, outputElement) {
|
|
/// <summary>Modular exponentiation in an integer group.</summary>
|
|
/// <param name="valueElement" type="integerGroupElement">The base input to the exponentiation.</param>
|
|
/// <param name="exponent" type="Array">The exponentas an unsigned integer.</param>
|
|
/// <param name="outputElement" type="integerGroupElement" optional="true">Output element that takes the modular exponentiation result.</param>
|
|
/// <returns type="integerGroupElement">Computed result. Same as <paramref name="outputElement"/> if not null, a new object otherwise.</returns>
|
|
|
|
outputElement = outputElement || integerGroupElement([], this);
|
|
|
|
// If exponent is 0 return 1
|
|
if (compareDigits(exponent, m_zero) === 0) {
|
|
outputElement.m_digits = intToDigits(1, this.m_digitWidth);
|
|
} else if (compareDigits(exponent, m_one) === 0) {
|
|
// If exponent is 1 return valueElement
|
|
for (var i = 0; i < valueElement.m_digits.length; i++) {
|
|
outputElement.m_digits[i] = valueElement.m_digits[i];
|
|
}
|
|
outputElement.m_digits.length = valueElement.m_digits.length;
|
|
} else {
|
|
this.montmul.modExp(
|
|
valueElement.m_digits,
|
|
exponent,
|
|
outputElement.m_digits);
|
|
outputElement.m_digits.length = this.montmul.s;
|
|
}
|
|
|
|
return outputElement;
|
|
}
|
|
|
|
function integerGroupElement(digits, group) {
|
|
/// <summary>integerGroupElement inner class.
|
|
/// Create a new integer element mod n.
|
|
/// </summary>
|
|
/// <param name="digits" type="Array">
|
|
/// An array of digits representing the element.
|
|
/// </param>
|
|
/// <param name="group" type="IntegerGroup">
|
|
/// The parent group to which this element belongs.
|
|
/// </param>
|
|
|
|
// The value given in digits
|
|
// must be >= 0 and &;lt; modulus. Note that the constructor should not be
|
|
// visible to the user, user should use group.createElementFromDigits(). This way we
|
|
// can use any digit size and endian-ness we wish internally, operating in
|
|
// our chosen representation until such time as the user wishes to produce
|
|
// a byte array as output, which will be done by calling
|
|
// toByteArrayUnsigned(). Note that other properties and methods are meant
|
|
// to be "public" of course and thus callable by the user.
|
|
|
|
return {
|
|
// Variables
|
|
m_digits: digits,
|
|
m_group: group,
|
|
|
|
// Functions
|
|
equals: function (element) {
|
|
/// <summary>Compare an elements to this for equality.</summary>
|
|
/// <param name="element" type="integerGroupElement">Element to compare.</param>
|
|
/// <returns>True if elements are equal, false otherwise.</returns>
|
|
return (compareDigits(this.m_digits, element.m_digits) === 0) &&
|
|
this.m_group.equals(this.m_group, element.m_group);
|
|
}
|
|
};
|
|
}
|
|
|
|
return {
|
|
// Variables
|
|
m_modulus: m_modulus,
|
|
m_digitWidth: m_digitWidth,
|
|
montmul: montmul,
|
|
|
|
// Functions
|
|
createElementFromInteger: createElementFromInteger,
|
|
createElementFromBytes: createElementFromBytes,
|
|
createElementFromDigits: createElementFromDigits,
|
|
equals: equals,
|
|
add: add,
|
|
subtract: subtract,
|
|
multiply: multiply,
|
|
inverse: inverse,
|
|
modexp: modexp
|
|
};
|
|
}
|
|
|
|
return {
|
|
DIGIT_BITS: DIGIT_BITS,
|
|
DIGIT_NUM_BYTES: DIGIT_NUM_BYTES,
|
|
DIGIT_MASK: DIGIT_MASK,
|
|
DIGIT_BASE: DIGIT_BASE,
|
|
DIGIT_MAX: DIGIT_MAX,
|
|
Zero: Zero,
|
|
One: One,
|
|
|
|
normalizeDigitArray: normalizeDigitArray,
|
|
swapEndianness: swapEndianness,
|
|
bytesToDigits: bytesToDigits,
|
|
stringToDigits: stringToDigits,
|
|
digitsToString: digitsToString,
|
|
intToDigits: intToDigits,
|
|
digitsToBytes: digitsToBytes,
|
|
sequenceEqual: sequenceEqual,
|
|
isZero: isZero,
|
|
isEven: isEven,
|
|
|
|
powerOfTwo: powerOfTwo,
|
|
shiftRight: shiftRight,
|
|
shiftLeft: shiftLeft,
|
|
compareDigits: compareDigits,
|
|
computeBitArray: computeBitArray,
|
|
bitLength: highestSetBit,
|
|
|
|
fixedWindowRecode: fixedWindowRecode,
|
|
IntegerGroup: IntegerGroup,
|
|
|
|
add: add,
|
|
subtract: subtract,
|
|
multiply: multiply,
|
|
divRem: divRem,
|
|
reduce: reduce,
|
|
modInv: modInv,
|
|
modExp: modExp,
|
|
modMul: modMul,
|
|
MontgomeryMultiplier: MontgomeryMultiplier,
|
|
gcd: gcd
|
|
};
|
|
}
|
|
|
|
var cryptoMath = cryptoMath || msrcryptoMath();
|
|
|
|
/// cryptoECC.js ==================================================================================
|
|
/// Implementation of Elliptic Curve math routines for cryptographic applications.
|
|
|
|
function MsrcryptoECC() {
|
|
/// <summary>Elliptic Curve Cryptography (ECC) functions.</summary>
|
|
|
|
// Create an array, mimics the constructors for typed arrays.
|
|
function createArray( parameter) {
|
|
var i, array = null;
|
|
if (!arguments.length || typeof arguments[0] === "number") {
|
|
// A number.
|
|
array = [];
|
|
for (i = 0; i < parameter; i += 1) {
|
|
array[i] = 0;
|
|
}
|
|
} else if (typeof arguments[0] === "object") {
|
|
// An array or other index-able object
|
|
array = [];
|
|
for (i = 0; i < parameter.length; i += 1) {
|
|
array[i] = parameter[i];
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
var btd = cryptoMath.bytesToDigits;
|
|
|
|
var EllipticCurveFp = function (p1, a1, b1, order, gx, gy) {
|
|
/// <param name="p1" type="Digits"/>
|
|
/// <param name="a1" type="Digits"/>
|
|
/// <param name="b1" type="Digits"/>
|
|
/// <param name="order" type="Digits"/>
|
|
/// <param name="gx" type="Digits"/>
|
|
/// <param name="gy" type="Digits"/>
|
|
/// <returns type="EllipticCurveFp"/>
|
|
|
|
var fieldStorageBitLength = p1.length;
|
|
|
|
var generator = EllipticCurvePointFp(this, false, gx, gy, null, false);
|
|
|
|
return {
|
|
p: p1, // field prime
|
|
a: a1, // Weierstrass coefficient a
|
|
b: b1, // Weierstrass coefficient b
|
|
order: order, // EC group order
|
|
generator: generator, // EC group generator
|
|
allocatePointStorage: function () {
|
|
return EllipticCurvePointFp(
|
|
this,
|
|
false,
|
|
cryptoMath.intToDigits(0, fieldStorageBitLength),
|
|
cryptoMath.intToDigits(0, fieldStorageBitLength)
|
|
);
|
|
},
|
|
createPointAtInfinity: function () {
|
|
return EllipticCurvePointFp(
|
|
this,
|
|
true,
|
|
cryptoMath.intToDigits(0, fieldStorageBitLength),
|
|
cryptoMath.intToDigits(0, fieldStorageBitLength)
|
|
);
|
|
}
|
|
};
|
|
};
|
|
|
|
var createWeierstrassCurve = function (curveData) {
|
|
|
|
var newCurve = new EllipticCurveFp(
|
|
btd(curveData.p), // P
|
|
btd(curveData.a), // A
|
|
btd(curveData.b), // B
|
|
btd(curveData.order), // Order
|
|
btd(curveData.gx), // gX
|
|
btd(curveData.gy) // gy
|
|
);
|
|
|
|
newCurve.type = curveData.type;
|
|
newCurve.name = curveData.name;
|
|
newCurve.generator.curve = newCurve;
|
|
|
|
return newCurve;
|
|
};
|
|
|
|
var createTedCurve = function (curveData) {
|
|
|
|
var btd = cryptoMath.bytesToDigits;
|
|
|
|
var newCurve = new EllipticCurveFp(
|
|
btd(curveData.p), // P
|
|
btd(curveData.a), // A
|
|
btd(curveData.d), // D
|
|
btd(curveData.order), // Order
|
|
btd(curveData.gx), // gX
|
|
btd(curveData.gy) // gy
|
|
);
|
|
|
|
newCurve.type = curveData.type;
|
|
|
|
if (newCurve.type == 1) {
|
|
newCurve.d = newCurve.b.slice();
|
|
delete newCurve.b;
|
|
}
|
|
|
|
newCurve.rbits = curveData.info[2];
|
|
newCurve.name = curveData.name;
|
|
newCurve.generator.curve = newCurve;
|
|
|
|
return newCurve;
|
|
};
|
|
|
|
var EllipticCurvePointFp = function (curve, isInfinity, x, y, z, isInMontgomeryForm) {
|
|
/// <param name="curve" type="EllipticCurveFp"/>
|
|
/// <param name="isInfinity" type="Boolean"/>
|
|
/// <param name="x" type="Digits"/>
|
|
/// <param name="y" type="Digits"/>
|
|
/// <param name="z" type="Digits" optional="true"/>
|
|
/// <param name="isInMontgomeryForm" type="Boolean" optional="true"/>
|
|
/// <returns type="EllipticCurvePointFp"/>
|
|
|
|
var returnObj;
|
|
|
|
// 'optional' parameters
|
|
if (typeof z === "undefined") {
|
|
z = null;
|
|
}
|
|
|
|
if (typeof isInMontgomeryForm === "undefined") {
|
|
isInMontgomeryForm = false;
|
|
}
|
|
|
|
function equals( ellipticCurvePointFp) {
|
|
/// <param name="ellipticCurvePointFp" type="EllipticCurvePointFp"/>
|
|
|
|
// If null
|
|
if (!ellipticCurvePointFp) {
|
|
return false;
|
|
}
|
|
|
|
// Infinity == infinity
|
|
if (returnObj.isInfinity && ellipticCurvePointFp.isInfinity) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise its member-wise comparison
|
|
|
|
if (returnObj.z === null && ellipticCurvePointFp.z !== null) {
|
|
return false;
|
|
}
|
|
|
|
if (returnObj.z !== null && ellipticCurvePointFp.z === null) {
|
|
return false;
|
|
}
|
|
|
|
if (returnObj.z === null) {
|
|
return (cryptoMath.compareDigits(returnObj.x, ellipticCurvePointFp.x) === 0 &&
|
|
cryptoMath.compareDigits(returnObj.y, ellipticCurvePointFp.y) === 0 &&
|
|
returnObj.isInMontgomeryForm === ellipticCurvePointFp.isInMontgomeryForm);
|
|
}
|
|
|
|
return (cryptoMath.compareDigits(returnObj.x, ellipticCurvePointFp.x) === 0 &&
|
|
cryptoMath.compareDigits(returnObj.y, ellipticCurvePointFp.y) === 0 &&
|
|
cryptoMath.compareDigits(returnObj.z, ellipticCurvePointFp.z) === 0 &&
|
|
returnObj.isInMontgomeryForm === ellipticCurvePointFp.isInMontgomeryForm);
|
|
}
|
|
|
|
function copyTo( source, destination) {
|
|
/// <param name="source" type="EllipticCurvePointFp"/>
|
|
/// <param name="destination" type="EllipticCurvePointFp"/>
|
|
|
|
destination.curve = source.curve;
|
|
destination.x = source.x.slice();
|
|
destination.y = source.y.slice();
|
|
|
|
if (source.z !== null) {
|
|
destination.z = source.z.slice();
|
|
} else {
|
|
destination.z = null;
|
|
}
|
|
|
|
setterSupport || (destination.isAffine = source.isAffine);
|
|
destination.isInMontgomeryForm = source.isInMontgomeryForm;
|
|
destination.isInfinity = source.isInfinity;
|
|
|
|
if (!destination.equals(source)) {
|
|
throw new Error("Instances should be equal.");
|
|
}
|
|
|
|
}
|
|
|
|
function clone() {
|
|
|
|
var clonePoint = EllipticCurvePointFp(
|
|
returnObj.curve,
|
|
returnObj.isInfinity,
|
|
createArray(returnObj.x),
|
|
createArray(returnObj.y),
|
|
returnObj.z ? createArray(returnObj.z) : null,
|
|
returnObj.isInMontgomeryForm);
|
|
|
|
returnObj.ta && (clonePoint.ta = createArray(returnObj.ta));
|
|
returnObj.tb && (clonePoint.tb = createArray(returnObj.tb));
|
|
|
|
return clonePoint;
|
|
}
|
|
|
|
returnObj = {
|
|
equals: function (ellipticCurvePointFp) {
|
|
return equals(ellipticCurvePointFp);
|
|
},
|
|
copy: function (destination) {
|
|
copyTo(this, destination);
|
|
return;
|
|
},
|
|
clone: function () {
|
|
return clone();
|
|
}
|
|
};
|
|
|
|
createProperty(returnObj, "curve", curve, function () { return curve; }, function (val) { curve = val; });
|
|
|
|
createProperty(returnObj, "x", x, function () { return x; }, function (val) { x = val; });
|
|
createProperty(returnObj, "y", y, function () { return y; }, function (val) { y = val; });
|
|
createProperty(returnObj, "z", z, function () { return z; }, function (val) { z = val; });
|
|
|
|
createProperty(returnObj, "isInMontgomeryForm", isInMontgomeryForm, function () { return isInMontgomeryForm; }, function (val) { isInMontgomeryForm = val; });
|
|
createProperty(returnObj, "isInfinity", isInfinity, function () { return isInfinity; }, function (val) { isInfinity = val; });
|
|
createProperty(returnObj, "isAffine", (z === null), function () { return (z === null); });
|
|
|
|
return returnObj;
|
|
};
|
|
|
|
var EllipticCurveOperatorFp = function (curve) {
|
|
/// <param name="curve" type="EllipticCurveFp"/>
|
|
|
|
// Store a reference to the curve.
|
|
var m_curve = curve;
|
|
|
|
var tedCurve = (curve.type === 1);
|
|
|
|
var fieldElementWidth = curve.p.length;
|
|
|
|
var montgomeryMultiplier = cryptoMath.MontgomeryMultiplier(curve.p);
|
|
|
|
// Pre-compute and store the montgomeryized form of A, and set our
|
|
// zero flag to determine whether or not we should use implementations
|
|
// optimized for A = 0.
|
|
var montgomerizedA = curve.a.slice();
|
|
montgomeryMultiplier.convertToMontgomeryForm(montgomerizedA);
|
|
|
|
var aequalsZero = cryptoMath.isZero(curve.a);
|
|
|
|
var one = cryptoMath.One;
|
|
|
|
var onemontgomery = createArray(fieldElementWidth);
|
|
onemontgomery[0] = 1;
|
|
montgomeryMultiplier.convertToMontgomeryForm(onemontgomery);
|
|
|
|
var group = cryptoMath.IntegerGroup(cryptoMath.digitsToBytes(montgomeryMultiplier.m), true);
|
|
|
|
// Setup temp storage.
|
|
var temp0 = createArray(fieldElementWidth);
|
|
var temp1 = createArray(fieldElementWidth);
|
|
var temp2 = createArray(fieldElementWidth);
|
|
var temp3 = createArray(fieldElementWidth);
|
|
var temp4 = createArray(fieldElementWidth);
|
|
var temp5 = createArray(fieldElementWidth);
|
|
var temp6 = createArray(fieldElementWidth);
|
|
var temp7 = createArray(fieldElementWidth);
|
|
var swap0 = createArray(fieldElementWidth);
|
|
|
|
// Some additional temp storage used in point conversion routines.
|
|
var conversionTemp0 = createArray(fieldElementWidth);
|
|
var conversionTemp1 = createArray(fieldElementWidth);
|
|
var conversionTemp2 = createArray(fieldElementWidth);
|
|
|
|
function modSub(left, right, result) {
|
|
var resultElement = group.createElementFromInteger(0);
|
|
resultElement.m_digits = result;
|
|
group.subtract(
|
|
group.createElementFromDigits(left),
|
|
group.createElementFromDigits(right),
|
|
resultElement);
|
|
}
|
|
|
|
function modAdd(left, right, result) {
|
|
var resultElement = group.createElementFromInteger(0);
|
|
resultElement.m_digits = result;
|
|
group.add(
|
|
group.createElementFromDigits(left),
|
|
group.createElementFromDigits(right),
|
|
resultElement);
|
|
}
|
|
|
|
function modInv(number, result) {
|
|
cryptoMath.modInv(number, m_curve.p, result);
|
|
}
|
|
|
|
function modDivByTwo( dividend, result) {
|
|
|
|
var s = dividend.length;
|
|
|
|
var modulus = curve.p;
|
|
|
|
// If dividend is odd, add modulus
|
|
if ((dividend[0] & 0x1) === 0x1) {
|
|
var carry = 0;
|
|
|
|
for (var i = 0; i < s; i += 1) {
|
|
carry += dividend[i] + modulus[i];
|
|
result[i] = carry & cryptoMath.DIGIT_MASK;
|
|
carry = (carry >>> cryptoMath.DIGIT_BITS);
|
|
}
|
|
|
|
// Put carry bit into position for masking in
|
|
carry = carry << (cryptoMath.DIGIT_BITS - 1);
|
|
|
|
// Bit shift
|
|
cryptoMath.shiftRight(result, result);
|
|
|
|
// Mask in the carry bit
|
|
result[s - 1] |= carry;
|
|
} else {
|
|
// Shift directly into result
|
|
cryptoMath.shiftRight(dividend, result);
|
|
}
|
|
|
|
}
|
|
|
|
function montgomeryMultiply(left, right, result) {
|
|
montgomeryMultiplier.montgomeryMultiply(
|
|
left,
|
|
right,
|
|
result);
|
|
}
|
|
|
|
function montgomerySquare(left, result) {
|
|
montgomeryMultiplier.montgomeryMultiply(
|
|
left,
|
|
left,
|
|
result);
|
|
}
|
|
|
|
function correctInversion(digits) {
|
|
/// <param name="digits" type="Digits"/>
|
|
var results = createArray(digits.length);
|
|
montgomeryMultiply(digits, montgomeryMultiplier.rCubedModm, results);
|
|
for (var i = 0; i < results.length; i += 1) {
|
|
digits[i] = results[i];
|
|
}
|
|
}
|
|
|
|
function doubleAequalsNeg3(point, outputPoint) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
// If point = infinity then outputPoint := infinity.
|
|
if (point.isInfinity) {
|
|
outputPoint.isInfinity = true;
|
|
return;
|
|
}
|
|
|
|
// t1 = z^2
|
|
montgomerySquare(point.z, temp1);
|
|
|
|
// t4 = zy
|
|
montgomeryMultiply(point.z, point.y, temp4);
|
|
|
|
// t2 = x + z^2
|
|
// t2 = x + t1
|
|
modAdd(point.x, temp1, temp2);
|
|
|
|
// t1 = x - z^2
|
|
// t1 = x - t1
|
|
modSub(point.x, temp1, temp1);
|
|
|
|
// Zfinal = zy
|
|
outputPoint.z = temp4.slice();
|
|
|
|
// t3 = (x + z^2)(x - z^2)
|
|
montgomeryMultiply(temp1, temp2, temp3);
|
|
|
|
// t2 = (x + z^2)(x - z^2)/2
|
|
modDivByTwo(temp3, temp2);
|
|
|
|
// t1 = alpha = 3(x + z^2)(x - z^2)/2
|
|
modAdd(temp3, temp2, temp1);
|
|
|
|
// t2 = y^2
|
|
montgomerySquare(point.y, temp2);
|
|
|
|
// t4 = alpha^2
|
|
montgomerySquare(temp1, temp4);
|
|
|
|
// t3 = beta = xy^2
|
|
montgomeryMultiply(point.x, temp2, temp3);
|
|
|
|
// t4 = alpha^2-beta
|
|
modSub(temp4, temp3, temp4);
|
|
|
|
// Xfinal = alpha^2-2beta
|
|
modSub(temp4, temp3, outputPoint.x);
|
|
|
|
// t4 = beta-Xfinal
|
|
modSub(temp3, outputPoint.x, temp4);
|
|
|
|
// t3 = y^4
|
|
montgomerySquare(temp2, temp3);
|
|
|
|
// t3 = y^4
|
|
montgomeryMultiply(temp1, temp4, temp2);
|
|
|
|
// Yfinal = alpha.(beta-Xfinal)-y^4
|
|
modSub(temp2, temp3, outputPoint.y);
|
|
|
|
// Finalize the flags on the output point.
|
|
outputPoint.isInfinity = false;
|
|
outputPoint.isInMontgomeryForm = true;
|
|
}
|
|
|
|
function doubleAequals0(point, outputPoint) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
// If point = infinity then outputPoint := infinity.
|
|
if (point.isInfinity) {
|
|
outputPoint.isInfinity = true;
|
|
return;
|
|
}
|
|
|
|
// 't3:=Y1^2;'
|
|
montgomerySquare(point.y, temp3);
|
|
|
|
// 't4:=X1^2;'
|
|
montgomerySquare(point.x, temp4);
|
|
|
|
// 't4:=3*t4;'
|
|
modAdd(temp4, temp4, temp0);
|
|
modAdd(temp0, temp4, temp4);
|
|
|
|
// 't5:=X1*t3;'
|
|
montgomeryMultiply(point.x, temp3, temp5);
|
|
|
|
// 't0:=t3^2;'
|
|
montgomerySquare(temp3, temp0);
|
|
|
|
// 't1:=t4/2;'
|
|
modDivByTwo(temp4, temp1);
|
|
|
|
// 't3:=t1^2;'
|
|
montgomerySquare(temp1, temp3);
|
|
|
|
// 'Z_out:=Y1*Z1;'
|
|
montgomeryMultiply(point.y, point.z, swap0);
|
|
for (var i = 0; i < swap0.length; i += 1) {
|
|
outputPoint.z[i] = swap0[i];
|
|
}
|
|
|
|
// 'X_out:=t3-2*t5;'
|
|
modSub(temp3, temp5, outputPoint.x);
|
|
modSub(outputPoint.x, temp5, outputPoint.x);
|
|
|
|
// 't4:=t5-X_out;'
|
|
modSub(temp5, outputPoint.x, temp4);
|
|
|
|
// 't2:=t1*t4;'
|
|
montgomeryMultiply(temp1, temp4, temp2);
|
|
|
|
// 'Y_out:=t2-t0;'
|
|
modSub(temp2, temp0, outputPoint.y);
|
|
|
|
// Finalize the flags on the output point.
|
|
outputPoint.isInfinity = false;
|
|
outputPoint.isInMontgomeryForm = true;
|
|
}
|
|
|
|
// Given a povar P on an elliptic curve, return a table of
|
|
// size 2^(w-2) filled with pre-computed values for
|
|
// P, 3P, 5P, ... Etc.
|
|
function generatePrecomputationTable(w, generatorPoint) {
|
|
/// <summary>Given a point P on an elliptic curve, return a table of
|
|
/// size 2^(w-2) filled with pre-computed values for
|
|
/// P, 3P, 5P, ... Etc.</summary>
|
|
/// <param name="w" type="Array">Window size</param>
|
|
/// <param name="generatorPoint" type="EllipticCurvePointFp"></param>
|
|
/// <returns type="Array">Precomputation table</returns>
|
|
|
|
var validationPoint = generatorPoint.clone();
|
|
convertToStandardForm(validationPoint);
|
|
if (!validatePoint(validationPoint)) {
|
|
throw new Error("Invalid Parameter");
|
|
}
|
|
|
|
// Create a Jacobian clone
|
|
var pointJac = generatorPoint.clone();
|
|
convertToJacobianForm(pointJac);
|
|
|
|
var tablePos = [generatorPoint.clone()];
|
|
|
|
// Q := P;
|
|
var qJac = pointJac.clone();
|
|
|
|
// Px2 = 2 * P
|
|
var px2 = pointJac.clone();
|
|
double(pointJac, px2);
|
|
convertToAffineForm(px2);
|
|
|
|
var qAff;
|
|
|
|
for (var i = 1; i < Math.pow(2, w - 2) ; i++) {
|
|
|
|
//Q := Q+P2;
|
|
mixedAdd(qJac, px2, qJac);
|
|
|
|
qAff = qJac.clone();
|
|
convertToAffineForm(qAff);
|
|
|
|
tablePos[i] = qAff;
|
|
}
|
|
|
|
return tablePos;
|
|
}
|
|
|
|
function double(point, outputPoint) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
if (typeof point === "undefined") {
|
|
throw new Error("point undefined");
|
|
}
|
|
if (typeof outputPoint === "undefined") {
|
|
throw new Error("outputPoint undefined");
|
|
}
|
|
|
|
//// if (!point.curve.equals(outputPoint.curve)) {
|
|
//// throw new Error("point and outputPoint must be from the same curve object.");
|
|
//// }
|
|
|
|
if (point.isAffine) {
|
|
throw new Error("Given point was in Affine form. Use convertToJacobian() first.");
|
|
}
|
|
|
|
if (!point.isInMontgomeryForm) {
|
|
throw new Error("Given point must be in Montgomery form. Use montgomeryize() first.");
|
|
}
|
|
|
|
// Currently we support only two curve types, those with A=-3, and
|
|
// those with A=0. In the future we will implement general support.
|
|
// For now we switch here, assuming that the curve was validated in
|
|
// the constructor.
|
|
if (aequalsZero) {
|
|
doubleAequals0(point, outputPoint);
|
|
} else {
|
|
doubleAequalsNeg3(point, outputPoint);
|
|
}
|
|
|
|
}
|
|
|
|
function mixedDoubleAdd(jacobianPoint, affinePoint, outputPoint) {
|
|
/// <param name="jacobianPoint" type="EllipticCurvePointFp"/>
|
|
/// <param name="affinePoint" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
if (jacobianPoint.isInfinity) {
|
|
affinePoint.copy(outputPoint);
|
|
this.convertToJacobianForm(outputPoint);
|
|
return;
|
|
}
|
|
|
|
if (affinePoint.isInfinity) {
|
|
jacobianPoint.copy(outputPoint);
|
|
return;
|
|
}
|
|
|
|
// Ok then we do the full double and add.
|
|
|
|
// Note: in pseudo-code the capital X,Y,Z is Jacobian point, lower
|
|
// case x, y, z is Affine point.
|
|
|
|
// 't5:=Z1^ 2;'
|
|
montgomerySquare(jacobianPoint.z, temp5);
|
|
|
|
// 't6:=Z1*t5;'
|
|
montgomeryMultiply(jacobianPoint.z, temp5, temp6);
|
|
|
|
// 't4:=x2*t5;'
|
|
montgomeryMultiply(affinePoint.x, temp5, temp4);
|
|
|
|
// 't5:=y2*t6;'
|
|
montgomeryMultiply(affinePoint.y, temp6, temp5);
|
|
|
|
// 't1:=t4-X1;'
|
|
modSub(temp4, jacobianPoint.x, temp1);
|
|
|
|
// 't2:=t5-Y1;'
|
|
modSub(temp5, jacobianPoint.y, temp2);
|
|
|
|
//if t1 eq 0 then
|
|
// if t2 eq 0 then
|
|
// X2,Y2,Z2 := DBL(X1,Y1,Z1,prime,rr,m,RR);
|
|
// return mADD(X2,Y2,Z2,x2,y2,prime,rr,m,RR);
|
|
// else
|
|
// return X1,Y1,Z1;
|
|
// end if;
|
|
//end if;
|
|
|
|
// 't4:=t2^2;'
|
|
montgomerySquare(temp2, temp4);
|
|
|
|
// 't6:=t1^2;'
|
|
montgomerySquare(temp1, temp6);
|
|
|
|
// 't5:=t6*X1;'
|
|
montgomeryMultiply(temp6, jacobianPoint.x, temp5);
|
|
|
|
// 't0:=t1*t6;'
|
|
montgomeryMultiply(temp1, temp6, temp0);
|
|
|
|
// 't3:=t4-2*t5;'
|
|
modSub(temp4, temp5, temp3);
|
|
modSub(temp3, temp5, temp3);
|
|
|
|
// 't4:=Z1*t1;'
|
|
montgomeryMultiply(jacobianPoint.z, temp1, temp4);
|
|
|
|
// 't3:=t3-t5;'
|
|
modSub(temp3, temp5, temp3);
|
|
|
|
// 't6:=t0*Y1;'
|
|
montgomeryMultiply(temp0, jacobianPoint.y, temp6);
|
|
|
|
// 't3:=t3-t0;'
|
|
modSub(temp3, temp0, temp3);
|
|
|
|
var temp3isZero = true;
|
|
|
|
for (var i = 0; i < temp3.length; i++) {
|
|
if (temp3[i] !== 0) {
|
|
temp3isZero = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (temp3isZero) {
|
|
for (i = 0; i < outputPoint.x.length; i++) {
|
|
outputPoint.x[i] = 0;
|
|
outputPoint.y[i] = 0;
|
|
outputPoint.z[i] = 0;
|
|
}
|
|
outputPoint.y[0] = 1;
|
|
return;
|
|
}
|
|
|
|
//if t3 eq 0 then
|
|
// return 0,1,0;
|
|
//end if;
|
|
|
|
// 't1:=2*t6;'
|
|
modAdd(temp6, temp6, temp1);
|
|
|
|
// 'Zout:=t4*t3;'
|
|
montgomeryMultiply(temp4, temp3, outputPoint.z);
|
|
|
|
// 't4:=t2*t3;'
|
|
montgomeryMultiply(temp2, temp3, temp4);
|
|
|
|
// 't0:=t3^2;'
|
|
montgomerySquare(temp3, temp0);
|
|
|
|
// 't1:=t1+t4;'
|
|
modAdd(temp1, temp4, temp1);
|
|
|
|
// 't4:=t0*t5;'
|
|
montgomeryMultiply(temp0, temp5, temp4);
|
|
|
|
// 't7:=t1^2;'
|
|
montgomerySquare(temp1, temp7);
|
|
|
|
// 't4:=t0*t5;'
|
|
montgomeryMultiply(temp0, temp3, temp5);
|
|
|
|
// 'Xout:=t7-2*t4;'
|
|
modSub(temp7, temp4, outputPoint.x);
|
|
modSub(outputPoint.x, temp4, outputPoint.x);
|
|
|
|
// 'Xout:=Xout-t5;'
|
|
modSub(outputPoint.x, temp5, outputPoint.x);
|
|
|
|
// 't3:=Xout-t4;'
|
|
modSub(outputPoint.x, temp4, temp3);
|
|
|
|
// 't0:=t5*t6;'
|
|
montgomeryMultiply(temp5, temp6, temp0);
|
|
|
|
// 't4:=t1*t3;'
|
|
montgomeryMultiply(temp1, temp3, temp4);
|
|
|
|
// 'Yout:=t4-t0;'
|
|
modSub(temp4, temp0, outputPoint.y);
|
|
|
|
outputPoint.isInfinity = false;
|
|
outputPoint.isInMontgomeryForm = true;
|
|
|
|
}
|
|
|
|
function mixedAdd(jacobianPoint, affinePoint, outputPoint) {
|
|
/// <param name="jacobianPoint" type="EllipticCurvePointFp"/>
|
|
/// <param name="affinePoint" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
if (jacobianPoint === null) {
|
|
throw new Error("jacobianPoint");
|
|
}
|
|
|
|
if (affinePoint === null) {
|
|
throw new Error("affinePoint");
|
|
}
|
|
|
|
if (outputPoint === null) {
|
|
throw new Error("outputPoint");
|
|
}
|
|
|
|
if (jacobianPoint.curve !== affinePoint.curve ||
|
|
jacobianPoint.curve !== outputPoint.curve) {
|
|
throw new Error("All points must be from the same curve object.");
|
|
}
|
|
|
|
if (jacobianPoint.isAffine) {
|
|
throw new Error(
|
|
"Given jacobianPoint was in Affine form. Use ConvertToJacobian() before calling DoubleJacobianAddAffinePoints().");
|
|
}
|
|
|
|
if (!affinePoint.isAffine) {
|
|
throw new Error(
|
|
"Given affinePoint was in Jacobian form. Use ConvertToAffine() before calling DoubleJacobianAddAffinePoints().");
|
|
}
|
|
|
|
if (outputPoint.isAffine) {
|
|
throw new Error(
|
|
"Given jacobianPoint was in Jacobian form. Use ConvertToJacobian() before calling DoubleJacobianAddAffinePoints().");
|
|
}
|
|
|
|
if (!jacobianPoint.isInMontgomeryForm) {
|
|
throw new Error("Jacobian point must be in Montgomery form");
|
|
}
|
|
|
|
if (!affinePoint.isInMontgomeryForm) {
|
|
throw new Error("Affine point must be in Montgomery form");
|
|
}
|
|
|
|
if (jacobianPoint.isInfinity) {
|
|
affinePoint.copy(outputPoint);
|
|
this.convertToJacobianForm(outputPoint);
|
|
return;
|
|
}
|
|
|
|
if (affinePoint.isInfinity) {
|
|
jacobianPoint.copy(outputPoint);
|
|
return;
|
|
}
|
|
|
|
// Ok then we do the full double and add.
|
|
|
|
// Note: in pseudo-code the capital X1,Y1,Z1 is Jacobian point,
|
|
// lower case x2, y2, z2 is Affine point.
|
|
|
|
//if (X1 eq 0) and (Y1 eq 1) and (Z1 eq 0) then
|
|
// z2 := ToMontgomery(1,prime,rr,m,RR);
|
|
// return x2,y2;
|
|
//end if;
|
|
//if (x2 eq 0) and (y2 eq 1) then
|
|
// return X1,Y1,Z1;
|
|
//end if;
|
|
|
|
// 't1 := Z1^2;'.
|
|
montgomerySquare(jacobianPoint.z, temp1);
|
|
|
|
// 't2 := t1 * Z1;'
|
|
montgomeryMultiply(temp1, jacobianPoint.z, temp2);
|
|
|
|
// 't3 := t1 * x2;'
|
|
montgomeryMultiply(temp1, affinePoint.x, temp3);
|
|
|
|
// 't4 := t2 * y2;'
|
|
montgomeryMultiply(temp2, affinePoint.y, temp4);
|
|
|
|
// 't1 := t3 - X1;'
|
|
modSub(temp3, jacobianPoint.x, temp1);
|
|
|
|
// 't2 := t4 - Y1;'
|
|
modSub(temp4, jacobianPoint.y, temp2);
|
|
|
|
// If t1 != 0 then
|
|
var i;
|
|
for (i = 0; i < temp1.length; i += 1) {
|
|
if (temp1[i] !== 0) {
|
|
|
|
// 'Zout := Z1 * t1;'
|
|
montgomeryMultiply(jacobianPoint.z, temp1, temp0);
|
|
for (var j = 0; j < fieldElementWidth; j += 1) {
|
|
outputPoint.z[j] = temp0[j];
|
|
}
|
|
|
|
// 't3 := t1^2;'
|
|
montgomerySquare(temp1, temp3);
|
|
|
|
// 't4 := t3 * t1;'
|
|
montgomeryMultiply(temp3, temp1, temp4);
|
|
|
|
// 't5 := t3 * X1;'
|
|
montgomeryMultiply(temp3, jacobianPoint.x, temp5);
|
|
|
|
// 't1 := 2 * t5;'
|
|
modAdd(temp5, temp5, temp1);
|
|
|
|
// 'Xout := t2^2;'
|
|
montgomerySquare(temp2, outputPoint.x);
|
|
|
|
// 'Xout := Xout - t1;'
|
|
modSub(outputPoint.x, temp1, outputPoint.x);
|
|
|
|
// 'Xout := Xout - t4;'
|
|
modSub(outputPoint.x, temp4, outputPoint.x);
|
|
|
|
// 't3 := t5 - Xout;'
|
|
modSub(temp5, outputPoint.x, temp3);
|
|
|
|
// 't5 := t3*t2;'
|
|
montgomeryMultiply(temp2, temp3, temp5);
|
|
|
|
// 't6 := t4*Y1;'
|
|
montgomeryMultiply(jacobianPoint.y, temp4, temp6);
|
|
|
|
// 'Yout := t5-t6;'
|
|
modSub(temp5, temp6, outputPoint.y);
|
|
|
|
outputPoint.isInfinity = false;
|
|
outputPoint.isInMontgomeryForm = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Else if T2 != 0 then
|
|
for (i = 0; i < temp2.length; i += 1) {
|
|
if (temp2[i] !== 0) {
|
|
// Return infinity
|
|
outputPoint.isInfinity = true;
|
|
outputPoint.isInMontgomeryForm = true;
|
|
return;
|
|
}
|
|
}
|
|
// Else use DBL routine to return 2(x2, y2, 1)
|
|
affinePoint.copy(outputPoint);
|
|
this.convertToJacobianForm(outputPoint);
|
|
this.double(outputPoint, outputPoint);
|
|
outputPoint.isInMontgomeryForm = true;
|
|
|
|
}
|
|
|
|
function scalarMultiply(k, point, outputPoint, muliplyBy4) {
|
|
/// <param name="k" type="Digits"/>
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
// Special case for the point at infinity or k == 0
|
|
if (point.isInfinity || cryptoMath.isZero(k)) {
|
|
outputPoint.isInfinity = true;
|
|
return;
|
|
}
|
|
|
|
// Runtime check for 1 <= k < order to ensure we don't get hit by
|
|
// subgroup attacks. Since k is a FixedWidth it is a positive integer
|
|
// and we already checked for zero above. So it must be >= 1 already.
|
|
if (cryptoMath.compareDigits(k, curve.order) >= 0) {
|
|
throw new Error("The scalar k must be in the range 1 <= k < order.");
|
|
}
|
|
|
|
// copy k so we can modify it without modifying the passed in array.
|
|
k = k.slice();
|
|
|
|
if (point.curve.type === 1) {
|
|
|
|
var pointIsEP = (typeof point.ta !== 'undefined');
|
|
|
|
if (!pointIsEP) {
|
|
convertToExtendedProjective(point);
|
|
}
|
|
|
|
scalarMultiplyTed(k, point, outputPoint, muliplyBy4);
|
|
|
|
// Convert the points back to standard if they arrived that way.
|
|
if (!pointIsEP) {
|
|
normalizeTed(point);
|
|
}
|
|
|
|
} else {
|
|
|
|
var pointIsMF = point.isInMontgomeryForm,
|
|
outputIsMF = outputPoint.isInMontgomeryForm,
|
|
outputIsAffine = outputPoint.isAffine;
|
|
|
|
// Convert parameters to Montgomory form if not already.
|
|
if (!pointIsMF) {
|
|
convertToMontgomeryForm(point);
|
|
}
|
|
|
|
if (!outputIsMF) {
|
|
convertToMontgomeryForm(outputPoint);
|
|
}
|
|
|
|
scalarMultiplyW(k, point, outputPoint);
|
|
|
|
// outputPoint returns as Jacobian - convert back to original state.
|
|
if (outputIsAffine) {
|
|
convertToAffineForm(outputPoint);
|
|
}
|
|
|
|
// Convert the points back to standard if they arrived that way.
|
|
if (!pointIsMF) {
|
|
convertToStandardForm(point);
|
|
}
|
|
|
|
if (!outputIsMF) {
|
|
convertToStandardForm(outputPoint);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
function scalarMultiplyW(k, point, outputPoint) {
|
|
/// <param name="k" type="Digits"/>
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp"/>
|
|
|
|
// The point should be in Montgomery form.
|
|
var validationPoint = point.clone();
|
|
convertToStandardForm(validationPoint);
|
|
|
|
if (!validatePoint(validationPoint)) {
|
|
throw new Error("Invalid Parameters.");
|
|
}
|
|
|
|
var odd = (k[0] & 1),
|
|
tempk = [];
|
|
|
|
// If (odd) then k = temp else k = k
|
|
modSub(point.curve.order, k, tempk);
|
|
for (i = 0; i < k.length; i++) {
|
|
k[i] = ((odd - 1) & (k[i] ^ tempk[i])) ^ k[i];
|
|
}
|
|
|
|
// Change w based on the size of the digits,
|
|
// 5 is good for 256 bits, use 6 for bigger sizes.
|
|
var w = (fieldElementWidth <= 8) ? 5 : 6;
|
|
var m = point.curve.p.length * cryptoMath.DIGIT_BITS;
|
|
var t = Math.ceil(m / (w - 1));
|
|
|
|
var kDigits = cryptoMath.fixedWindowRecode(k, w, t);
|
|
|
|
var Tm = generatePrecomputationTable(w, point);
|
|
|
|
var position =
|
|
Math.floor(Math.abs(kDigits[t]) - 1) / 2;
|
|
|
|
var Q = Tm[position].clone();
|
|
convertToJacobianForm(Q);
|
|
|
|
for (var i = t - 1; i >= 0; i--) {
|
|
|
|
for (var j = 0; j < (w - 2) ; j++) {
|
|
double(Q, Q);
|
|
}
|
|
|
|
position = Math.floor((Math.abs(kDigits[i]) - 1) / 2);
|
|
|
|
var L = tableLookupW(Tm, position);
|
|
|
|
if (kDigits[i] < 0) {
|
|
negate(L, L);
|
|
}
|
|
|
|
mixedDoubleAdd(Q, L, Q);
|
|
|
|
}
|
|
|
|
// if k is even, negate Q
|
|
modSub(point.curve.p, Q.y, tempk);
|
|
for (i = 0; i < Q.y.length; i++) {
|
|
Q.y[i] = ((odd - 1) & (Q.y[i] ^ tempk[i])) ^ Q.y[i];
|
|
}
|
|
|
|
Q.copy(outputPoint);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
function tableLookupW(table, index) {
|
|
|
|
var pos = (index + 1) % table.length;
|
|
|
|
for (var i = 0; i < table.length; i++) {
|
|
var L = table[pos].clone();
|
|
pos = (pos + 1) % table.length;
|
|
}
|
|
|
|
return L;
|
|
}
|
|
|
|
function negate(point, outputPoint) {
|
|
/// <param name="point" type="EllipticCurvePointFp">Input point to negate.</param>
|
|
/// <param name="outputPoint" type="EllipticCurvePointFp">(x, p - y).</param>
|
|
|
|
if (point !== outputPoint) {
|
|
point.copy(outputPoint);
|
|
}
|
|
modSub(point.curve.p, point.y, outputPoint.y);
|
|
}
|
|
|
|
function convertToMontgomeryForm(point) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
|
|
if (point.isInMontgomeryForm) {
|
|
throw new Error("The given point is already in Montgomery form.");
|
|
}
|
|
|
|
if (!point.isInfinity) {
|
|
montgomeryMultiplier.convertToMontgomeryForm(point.x);
|
|
montgomeryMultiplier.convertToMontgomeryForm(point.y);
|
|
|
|
if (point.z !== null) {
|
|
montgomeryMultiplier.convertToMontgomeryForm(point.z);
|
|
}
|
|
|
|
if (typeof point.ta !== 'undefined') {
|
|
montgomeryMultiplier.convertToMontgomeryForm(point.ta);
|
|
montgomeryMultiplier.convertToMontgomeryForm(point.tb);
|
|
}
|
|
}
|
|
|
|
point.isInMontgomeryForm = true;
|
|
}
|
|
|
|
function convertToStandardForm(point) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
|
|
if (!point.isInMontgomeryForm) {
|
|
throw new Error("The given point is not in montgomery form.");
|
|
}
|
|
|
|
if (!point.isInfinity) {
|
|
montgomeryMultiplier.convertToStandardForm(point.x);
|
|
montgomeryMultiplier.convertToStandardForm(point.y);
|
|
if (point.z !== null) {
|
|
montgomeryMultiplier.convertToStandardForm(point.z);
|
|
}
|
|
if (typeof point.ta !== 'undefined') {
|
|
montgomeryMultiplier.convertToStandardForm(point.ta);
|
|
montgomeryMultiplier.convertToStandardForm(point.tb);
|
|
}
|
|
}
|
|
|
|
point.isInMontgomeryForm = false;
|
|
|
|
}
|
|
|
|
function convertToAffineForm(point) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
|
|
if (point.isInfinity) {
|
|
point.z = null;
|
|
setterSupport || (point.isAffine = true);
|
|
return;
|
|
}
|
|
|
|
// DETERMINE 1/Z IN MONTGOMERY FORM --------------------------------
|
|
|
|
// Call out to the basic inversion function, not the one in this class.
|
|
cryptoMath.modInv(point.z, curve.p, conversionTemp2, true);
|
|
|
|
if (point.isInMontgomeryForm) {
|
|
montgomeryMultiply(conversionTemp2, montgomeryMultiplier.rCubedModm, conversionTemp1);
|
|
var swap = conversionTemp2;
|
|
conversionTemp2 = conversionTemp1;
|
|
conversionTemp1 = swap;
|
|
}
|
|
|
|
// CONVERT TO AFFINE COORDS ----------------------------------------
|
|
|
|
// 'temp0 <- 1/z^2'
|
|
montgomerySquare(conversionTemp2, conversionTemp0);
|
|
|
|
// Compute point.x = x / z^2 mod p
|
|
// NOTE: We cannot output directly to the X digit array since it is
|
|
// used for input to the multiplication routine, so we output to temp1
|
|
// and copy.
|
|
montgomeryMultiply(point.x, conversionTemp0, conversionTemp1);
|
|
for (var i = 0; i < fieldElementWidth; i += 1) {
|
|
point.x[i] = conversionTemp1[i];
|
|
}
|
|
|
|
// Compute point.y = y / z^3 mod p
|
|
// temp1 <- y * 1/z^2.
|
|
montgomeryMultiply(point.y, conversionTemp0, conversionTemp1);
|
|
// 'y <- temp1 * temp2 (which == 1/z)'
|
|
montgomeryMultiply(conversionTemp1, conversionTemp2, point.y);
|
|
|
|
// Finally, point.z = z / z mod p = 1
|
|
// We use z = NULL for this case to make detecting Jacobian form
|
|
// faster (otherwise we would have to scan the entire Z digit array).
|
|
point.z = null;
|
|
|
|
delete point.ta;
|
|
delete point.tb;
|
|
|
|
setterSupport || (point.isAffine = true);
|
|
}
|
|
|
|
function convertToJacobianForm(point) {
|
|
/// <param name="point" type="EllipticCurvePointFp"/>
|
|
|
|
if (!point.isAffine) {
|
|
throw new Error("The given point is not in Affine form.");
|
|
}
|
|
|
|
setterSupport || (point.isAffine = false);
|
|
|
|
var clonedDigits,
|
|
i,
|
|
zOne = point.isInMontgomeryForm ? onemontgomery : one;
|
|
|
|
clonedDigits = createArray(zOne.length);
|
|
for (i = 0; i < zOne.length; i += 1) {
|
|
clonedDigits[i] = zOne[i];
|
|
}
|
|
|
|
point.z = clonedDigits;
|
|
|
|
return;
|
|
}
|
|
|
|
function validatePoint(point) {
|
|
/// <summary>
|
|
/// Point validation
|
|
// Check if point P=(x,y) lies on the curve and if x,y are in [0, p-1]
|
|
/// </summary>
|
|
|
|
if (point.isInfinity) {
|
|
return false;
|
|
}
|
|
|
|
// Does P lie on the curve?
|
|
cryptoMath.modMul(point.y, point.y, point.curve.p, temp1)
|
|
|
|
cryptoMath.modMul(point.x, point.x, point.curve.p, temp2);
|
|
cryptoMath.modMul(point.x, temp2, point.curve.p, temp3);
|
|
modAdd(temp3, point.curve.b, temp2);
|
|
cryptoMath.modMul(point.x, point.curve.a, point.curve.p, temp3);
|
|
modAdd(temp2, temp3, temp2);
|
|
modSub(temp1, temp2, temp1);
|
|
|
|
if (cryptoMath.isZero(temp1) == false) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Ted functions
|
|
|
|
function validatePointTed(point) {
|
|
|
|
if (point.ta) {
|
|
point = point.clone();
|
|
normalizeTed(point);
|
|
}
|
|
|
|
// Does P lie on the curve?
|
|
cryptoMath.modMul(point.y, point.y, point.curve.p, temp3);
|
|
cryptoMath.modMul(point.x, point.x, point.curve.p, temp2);
|
|
|
|
cryptoMath.add(temp2, temp3, temp1);
|
|
cryptoMath.reduce(temp4, point.curve.p, temp4);
|
|
|
|
cryptoMath.modMul(temp2, temp3, point.curve.p, temp4);
|
|
cryptoMath.modMul(point.curve.d, temp4, point.curve.p, temp3);
|
|
|
|
cryptoMath.add(temp3, [1], temp2);
|
|
cryptoMath.reduce(temp2, point.curve.p, temp2);
|
|
|
|
cryptoMath.subtract(temp1, temp2, temp1);
|
|
cryptoMath.reduce(temp1, point.curve.p, temp1);
|
|
|
|
if (cryptoMath.isZero(temp1) == false) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function generatePrecomputationTableTed(npoints, point) {
|
|
|
|
// Precomputation function, points are stored using representation (X,Y,Z,dT)
|
|
// Twisted Edwards a=1 curve
|
|
|
|
var Q = point.clone(),
|
|
P2 = Q.clone(),
|
|
T = [];
|
|
|
|
// Generating P2 = 2(X1,Y1,Z1,T1a,T1b) -> (XP2,YP2,ZP2,d*TP2) and T[0] = P = (X1,Y1,Z1,T1a,T1b)
|
|
T[0] = convert_R1_to_R2(point);
|
|
doubleTed(Q, Q);
|
|
P2 = convert_R1_to_R2(Q);
|
|
Q = point.clone();
|
|
|
|
for (var i = 1; i < npoints; i++) {
|
|
// T[i] = 2P+T[i-1] = (2*i+1)P = (XP2,Y2P,ZP2,d*TP2) + (X_(2*i-1), Y_(2*i-1), Z_(2*i-1), Ta_(2*i-1), Tb_(2*i-1)) = (X_(2*i+1), Y_(2*i+1), Z_(2*i+1), d*T_(2*i+1))
|
|
addTedExtended(P2, Q, Q);
|
|
T[i] = convert_R1_to_R2(Q);
|
|
}
|
|
|
|
return T;
|
|
}
|
|
|
|
function convertToExtendedProjective(affinePoint) {
|
|
affinePoint.ta = affinePoint.x.slice();
|
|
affinePoint.tb = affinePoint.y.slice();
|
|
affinePoint.z = [1];
|
|
}
|
|
|
|
function scalarMultiplyTed(k, point, outputPoint, multiplyBy4) {
|
|
|
|
if (!validatePointTed(point)) {
|
|
throw new Error("Invalid Parameter");
|
|
}
|
|
|
|
var rbits = point.curve.rbits;
|
|
multiplyBy4 = typeof multiplyBy4 === 'undefined' ? true : multiplyBy4;
|
|
|
|
var w = (fieldElementWidth <= 8) ? 5 : 6;
|
|
|
|
var t = Math.floor((rbits + (w - 2)) / (w - 1));
|
|
var i, j;
|
|
|
|
// copy k so we can modify it without modifying the passed in array.
|
|
k = k.slice();
|
|
|
|
var T = point.clone();
|
|
|
|
convertToExtendedProjective(T);
|
|
|
|
if (multiplyBy4) {
|
|
doubleTed(T, T);
|
|
doubleTed(T, T);
|
|
}
|
|
|
|
var precomputationTable = generatePrecomputationTableTed(1 << (w - 2), T);
|
|
|
|
var odd = (k[0] & 1),
|
|
tempk = [];
|
|
|
|
// If (odd) then k = temp else k = k
|
|
modSub(point.curve.order, k, tempk);
|
|
for (i = 0; i < k.length; i++) {
|
|
k[i] = ((odd - 1) & (k[i] ^ tempk[i])) ^ k[i];
|
|
}
|
|
|
|
var kDigits = cryptoMath.fixedWindowRecode(k, w, t);
|
|
|
|
var position =
|
|
Math.floor(Math.abs(kDigits[t]) - 1) / 2;
|
|
|
|
var R = precomputationTable[position];
|
|
|
|
T.x = R.x.slice();
|
|
T.y = R.y.slice();
|
|
T.z = R.z.slice();
|
|
|
|
for (i = t - 1; i >= 0; i--) {
|
|
|
|
for (j = 0; j < (w - 1) ; j++) {
|
|
doubleTed(T, T);
|
|
}
|
|
|
|
position = Math.floor((Math.abs(kDigits[i]) - 1) / 2);
|
|
|
|
var L = tableLookupTed(precomputationTable, position);
|
|
|
|
if (kDigits[i] < 0) {
|
|
modSub(point.curve.p, L.x, L.x);
|
|
modSub(point.curve.p, L.td, L.td);
|
|
}
|
|
|
|
addTedExtended(L, T, T);
|
|
}
|
|
|
|
// If (odd) then T.x = temp else T.x = T.x
|
|
modSub(point.curve.p, T.x, tempk);
|
|
for (i = 0; i < T.x.length; i++) {
|
|
T.x[i] = ((odd - 1) & (T.x[i] ^ tempk[i])) ^ T.x[i];
|
|
}
|
|
|
|
normalizeTed(T);
|
|
|
|
outputPoint.x = T.x.slice();
|
|
outputPoint.y = T.y.slice();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
function tableLookupTed(table, index) {
|
|
|
|
var pos = (index + 1) % table.length;
|
|
|
|
for (var i = 0; i < table.length; i++) {
|
|
var L = {
|
|
x: table[pos].x.slice(),
|
|
y: table[pos].y.slice(),
|
|
z: table[pos].z.slice(),
|
|
td: table[pos].td.slice()
|
|
}
|
|
pos = (pos + 1) % table.length;
|
|
}
|
|
|
|
return L;
|
|
}
|
|
|
|
function normalizeTed(point) {
|
|
|
|
cryptoMath.modInv(point.z, curve.p, conversionTemp2, true);
|
|
|
|
cryptoMath.modMul(point.x, conversionTemp2, curve.p, point.x);
|
|
|
|
cryptoMath.modMul(point.y, conversionTemp2, curve.p, point.y);
|
|
|
|
delete point.ta;
|
|
delete point.tb;
|
|
|
|
point.z = null;
|
|
|
|
return;
|
|
}
|
|
|
|
function doubleTed(point, outputPoint) {
|
|
|
|
if (typeof point.ta === 'undefined') {
|
|
throw new Error("Point should be in Extended Projective form.");
|
|
}
|
|
|
|
// t0 = x1^2
|
|
cryptoMath.modMul(point.x, point.x, point.curve.p, temp0);
|
|
|
|
// t1 = y1^2
|
|
cryptoMath.modMul(point.y, point.y, point.curve.p, temp1);
|
|
|
|
// Ta = z1^2
|
|
cryptoMath.modMul(point.z, point.z, point.curve.p, point.ta);
|
|
|
|
// (new) Tbfinal = Y1^2-X1^2
|
|
modSub(temp1, temp0, outputPoint.tb);
|
|
|
|
//(new) t0 = X1^2+Y1^2
|
|
modAdd(temp0, temp1, temp0);
|
|
|
|
//(ok) Ta = 2z1^2
|
|
modAdd(point.ta, point.ta, point.ta);
|
|
|
|
// (ok) y = 2y1
|
|
modAdd(point.y, point.y, point.y);
|
|
|
|
// (new) t1 = 2z1^2-(X1^2+Y1^2)
|
|
modSub(point.ta, temp0, temp1);
|
|
|
|
// Tafinal = 2x1y1
|
|
cryptoMath.modMul(point.x, point.y, point.curve.p, outputPoint.ta);
|
|
|
|
// Yfinal = (x1^2+y1^2)(y1^2-x1^2)
|
|
cryptoMath.modMul(temp0, outputPoint.tb, point.curve.p, outputPoint.y);
|
|
|
|
// Xfinal = 2x1y1[2z1^2-(y1^2-x1^2)]
|
|
cryptoMath.modMul(temp1, outputPoint.ta, point.curve.p, outputPoint.x);
|
|
|
|
// Zfinal = (y1^2-x1^2)[2z1^2-(y1^2-x1^2)]
|
|
cryptoMath.modMul(temp0, temp1, point.curve.p, outputPoint.z);
|
|
|
|
return;
|
|
}
|
|
|
|
function addTed(point1 /*Q*/, point2 /*P*/, outputPoint) {
|
|
|
|
var cm = cryptoMath;
|
|
|
|
var modulus = point1.curve.p;
|
|
var temp1 = [];
|
|
|
|
if (typeof point1.ta === 'undefined') {
|
|
throw new Error("Point1 should be in Extended Projective form.");
|
|
}
|
|
|
|
if (typeof point2.ta === 'undefined') {
|
|
throw new Error("Point2 should be in Extended Projective form.");
|
|
}
|
|
|
|
var qq = convert_R1_to_R2(point1);
|
|
|
|
addTedExtended(qq, point2, outputPoint);
|
|
|
|
return;
|
|
}
|
|
|
|
function convert_R1_to_R2(point) {
|
|
|
|
var curve = point.curve,
|
|
modulus = curve.p,
|
|
qq = {
|
|
x: point.x.slice(),
|
|
y: point.y.slice(),
|
|
z: point.z.slice(),
|
|
td: [],
|
|
curve: point.curve
|
|
};
|
|
|
|
cryptoMath.modMul(point.ta, point.tb, modulus, conversionTemp0);
|
|
|
|
cryptoMath.modMul(conversionTemp0, curve.d, modulus, qq.td);
|
|
|
|
return qq;
|
|
}
|
|
|
|
function addTedExtended(qq /*Q*/, point2 /*P*/, outputPoint) {
|
|
|
|
// Complete point addition P = P+Q, including the cases P!=Q, P=Q, P=-Q, P=neutral and Q=neutral
|
|
// Twisted Edwards a=1 curve
|
|
// Inputs: P = (X1,Y1,Z1,Ta,Tb), where T1 = Ta*Tb, corresponding to extended twisted Edwards coordinates (X1:Y1:Z1:T1)
|
|
// Q = (X2,Y2,Z2,dT2), corresponding to extended twisted Edwards coordinates (X2:Y2:Z2:T2)
|
|
// Output: P = (X1,Y1,Z1,Ta,Tb), where T1 = Ta*Tb, corresponding to extended twisted Edwards coordinates (X1:Y1:Z1:T1)
|
|
|
|
var cm = cryptoMath;
|
|
var modulus = point2.curve.p;
|
|
|
|
temp1 = []; temp2 = []; temp3 = [];
|
|
|
|
//FP_MUL(P->Z, Q->Z, t3); // t3 = Z1*Z2
|
|
cm.modMul(point2.z, qq.z, modulus, temp3);
|
|
|
|
//FP_MUL(P->Ta, P->Tb, t1); // t1 = T1
|
|
cm.modMul(point2.ta, point2.tb, modulus, temp1);
|
|
|
|
//FP_ADD(P->X, P->Y, P->Ta); // Ta = (X1+Y1)
|
|
modAdd(point2.x, point2.y, point2.ta);
|
|
|
|
//FP_MUL(t1, Q->Td, t2); // t2 = dT1*T2
|
|
cm.modMul(temp1, qq.td, modulus, temp2);
|
|
|
|
//FP_ADD(Q->X, Q->Y, P->Tb); // Tb = (X2+Y2)
|
|
modAdd(qq.x, qq.y, point2.tb);
|
|
|
|
//FP_SUB(t3, t2, t1); // t1 = theta
|
|
modSub(temp3, temp2, temp1);
|
|
|
|
//FP_ADD(t3, t2, t3); // t3 = alpha
|
|
modAdd(temp3, temp2, temp3);
|
|
|
|
//FP_MUL(P->Ta, P->Tb, t2); // t2 = (X1+Y1)(X2+Y2)
|
|
cm.modMul(point2.ta, point2.tb, modulus, temp2);
|
|
|
|
//FP_MUL(P->X, Q->X, P->Z); // Z = X1*X2
|
|
cm.modMul(point2.x, qq.x, modulus, point2.z);
|
|
|
|
//FP_MUL(P->Y, Q->Y, P->X); // X = Y1*Y2
|
|
cm.modMul(point2.y, qq.y, modulus, point2.x);
|
|
|
|
//FP_SUB(t2, P->Z, t2);
|
|
modSub(temp2, point2.z, temp2);
|
|
|
|
//FP_SUB(P->X, P->Z, P->Ta); // Tafinal = omega = Y1*Y2-X1*X2
|
|
modSub(point2.x, point2.z, outputPoint.ta);
|
|
|
|
//FP_SUB(t2, P->X, P->Tb); // Tbfinal = beta = (X1+Y1)(X2+Y2)-X1*X2-Y1*Y2
|
|
modSub(temp2, point2.x, outputPoint.tb);
|
|
|
|
//FP_MUL(P->Ta, t3, P->Y); // Yfinal = alpha*omega
|
|
cm.modMul(outputPoint.ta, temp3, modulus, outputPoint.y);
|
|
|
|
//FP_MUL(P->Tb, t1, P->X); // Xfinal = beta*theta
|
|
cm.modMul(outputPoint.tb, temp1, modulus, outputPoint.x);
|
|
|
|
//FP_MUL(t3, t1, P->Z); // Zfinal = theta*alpha
|
|
cm.modMul(temp3, temp1, modulus, outputPoint.z);
|
|
|
|
return;
|
|
}
|
|
|
|
function convertTedToWeierstrass(tedPoint, wPoint) {
|
|
/// <summary></summary>
|
|
/// <param name="tedPoint" type=""></param>
|
|
/// <param name="outputPoint" type=""></param>
|
|
|
|
var a = tedPoint.curve.a.slice(),
|
|
d = tedPoint.curve.d.slice(),
|
|
p = tedPoint.curve.p,
|
|
modMul = cryptoMath.modMul,
|
|
modInv = cryptoMath.modInv;
|
|
|
|
// t1 = 5
|
|
temp1 = [5];
|
|
|
|
// t2 = 5a
|
|
modMul(a, temp1, p, temp2);
|
|
|
|
// t2 = 5a-d
|
|
modSub(temp2, d, temp2);
|
|
|
|
// t3 = 5d
|
|
modMul(d, temp1, p, temp3);
|
|
|
|
// t1 = a-5d
|
|
modSub(a, temp3, temp1);
|
|
|
|
// t3 = yTE*(a-5d)
|
|
modMul(tedPoint.y, temp1, p, temp3);
|
|
|
|
// t2 = (5a-d) + yTE*(a-5d)
|
|
modAdd(temp3, temp2, temp2);
|
|
|
|
// t1 = 1
|
|
temp1 = [1];
|
|
|
|
// t3 = 1-yTE
|
|
modSub(temp1, tedPoint.y, temp3);
|
|
|
|
// t1 = 12
|
|
temp1 = [12];
|
|
|
|
// t4 = 12(1-yTE)
|
|
modMul(temp1, temp3, p, temp4);
|
|
|
|
// t4 = 1/12(1-yTE)
|
|
modInv(temp4, p, temp4, true);
|
|
|
|
// t1 = xTE*(1-yTE)
|
|
modMul(tedPoint.x, temp3, p, temp1);
|
|
|
|
// t3 = 2xTE*(1-yTE)
|
|
modAdd(temp1, temp1, temp3);
|
|
|
|
// t3 = 4xTE*(1-yTE)
|
|
modAdd(temp3, temp3, temp3);
|
|
|
|
// t3 = 1/4xTE*(1-yTE)
|
|
modInv(temp3, p, temp3, true);
|
|
|
|
// Xfinal = ((5a-d) + yTE*(a-5d))/12(1-yTE)
|
|
modMul(temp4, temp2, p, wPoint.x);
|
|
|
|
// t1 = 1
|
|
temp1 = [1];
|
|
|
|
// t1 = yTE+1
|
|
modAdd(tedPoint.y, temp1, temp1);
|
|
|
|
// t2 = a-d
|
|
modSub(a, d, temp2);
|
|
|
|
// t4 = (a-d)*(yTE+1)
|
|
modMul(temp1, temp2, p, temp4);
|
|
|
|
// Yfinal = ((a-d)*(yTE+1))/4xTE*(1-yTE)
|
|
modMul(temp4, temp3, p, wPoint.y);
|
|
|
|
return;
|
|
}
|
|
|
|
function convertWeierstrassToTed(wPoint, tedPoint) {
|
|
|
|
var a = tedPoint.curve.a.slice(),
|
|
d = tedPoint.curve.d.slice(),
|
|
p = tedPoint.curve.p,
|
|
modMul = cryptoMath.modMul,
|
|
modInv = cryptoMath.modInv;
|
|
|
|
modAdd(wPoint.x, wPoint.x, temp1);
|
|
|
|
modAdd(wPoint.x, temp1, temp1);
|
|
|
|
// t1 = 6xW
|
|
modAdd(temp1, temp1, temp1);
|
|
|
|
// t2 = 6xW - a
|
|
modSub(temp1, a, temp2);
|
|
|
|
// t2 = 6xW - a - d
|
|
modSub(temp2, d, temp2);
|
|
|
|
modAdd(wPoint.y, wPoint.y, temp3);
|
|
|
|
modAdd(wPoint.y, temp3, temp3);
|
|
|
|
// t3 = 6yW
|
|
modAdd(temp3, temp3, temp3);
|
|
|
|
// t3 = 1/6yW
|
|
modInv(temp3, p, temp3, true);
|
|
|
|
// Xfinal = (6xW - a - d)/6yW
|
|
modMul(temp2, temp3, p, tedPoint.x);
|
|
|
|
// t1 = 12xW
|
|
modAdd(temp1, temp1, temp1);
|
|
|
|
// t2 = 12xW + d
|
|
modAdd(temp1, d, temp2);
|
|
|
|
// t1 = 12xW + a
|
|
modAdd(temp1, a, temp1);
|
|
|
|
modAdd(a, a, temp3);
|
|
|
|
// t2 = 12xW + d - 2a
|
|
modSub(temp2, temp3, temp2);
|
|
|
|
// t2 = 12xW + d - 4a
|
|
modSub(temp2, temp3, temp2);
|
|
|
|
// t2 = 12xW + d - 5a
|
|
modSub(temp2, a, temp2);
|
|
|
|
modAdd(d, d, temp3);
|
|
|
|
// t1 = 12xW + a - 2d
|
|
modSub(temp1, temp3, temp1);
|
|
|
|
// t1 = 12xW + a - 4d
|
|
modSub(temp1, temp3, temp1);
|
|
|
|
// t1 = 12xW + a - 5d
|
|
modSub(temp1, d, temp1);
|
|
|
|
// t1 = 1/(12xW + a - 5d)
|
|
modInv(temp1, p, temp1, true);
|
|
|
|
// Yfinal = (12xW + d - 5a)/(12xW + a - 5d)
|
|
modMul(temp1, temp2, p, tedPoint.y);
|
|
|
|
return;
|
|
}
|
|
|
|
var methods = {
|
|
|
|
convertToMontgomeryForm: convertToMontgomeryForm,
|
|
|
|
convertToStandardForm: convertToStandardForm,
|
|
|
|
convertToAffineForm: convertToAffineForm,
|
|
|
|
convertToJacobianForm: convertToJacobianForm,
|
|
|
|
// For tests
|
|
generatePrecomputationTable: function (w, generatorPoint) {
|
|
/// <param name="w" type="Number"/>
|
|
/// <param name="generatorPoint" type="EllipticCurvePointFp"/>
|
|
|
|
return generatePrecomputationTable(w, generatorPoint);
|
|
}
|
|
|
|
};
|
|
|
|
if (tedCurve) {
|
|
|
|
methods.double = doubleTed;
|
|
methods.add = addTed;
|
|
methods.scalarMultiply = scalarMultiply;
|
|
methods.normalize = normalizeTed;
|
|
methods.convertToExtendedProjective = convertToExtendedProjective;
|
|
methods.convertTedToWeierstrass = convertTedToWeierstrass;
|
|
methods.convertWeierstrassToTed = convertWeierstrassToTed;
|
|
methods.generatePrecomputationTable = function (w, generatorPoint) {
|
|
/// <param name="w" type="Number"/>
|
|
/// <param name="generatorPoint" type="EllipticCurvePointFp"/>
|
|
|
|
return generatePrecomputationTableTed(w, generatorPoint);
|
|
};
|
|
} else {
|
|
|
|
methods.double = double;
|
|
methods.mixedDoubleAdd = mixedDoubleAdd;
|
|
methods.mixedAdd = mixedAdd;
|
|
methods.scalarMultiply = scalarMultiply;
|
|
methods.negate = negate;
|
|
}
|
|
|
|
return methods;
|
|
|
|
};
|
|
|
|
var sec1EncodingFp = function () {
|
|
return {
|
|
encodePoint: function ( point) {
|
|
/// <summary>Encode an EC point without compression.
|
|
/// This function encodes a given points into a bytes array containing 0x04 | X | Y, where X and Y are big endian bytes of x and y coordinates.</summary>
|
|
/// <param name="point" type="EllipticCurvePointFp">Input EC point to encode.</param>
|
|
/// <returns type="Array">A bytes array containing 0x04 | X | Y, where X and Y are big endian encoded x and y coordinates.</returns>
|
|
|
|
if (!point) {
|
|
throw new Error("point");
|
|
}
|
|
|
|
if (!point.isAffine) {
|
|
throw new Error("Point must be in affine form.");
|
|
}
|
|
|
|
if (point.isInMontgomeryForm) {
|
|
throw new Error("Point must not be in Montgomery form.");
|
|
}
|
|
|
|
if (point.isInfinity) {
|
|
return createArray(1); /* [0] */
|
|
} else {
|
|
var xOctetString = cryptoMath.digitsToBytes(point.x);
|
|
var yOctetString = cryptoMath.digitsToBytes(point.y);
|
|
var pOctetString = cryptoMath.digitsToBytes(point.curve.p); // just to get byte length of p
|
|
var mlen = pOctetString.length;
|
|
if (mlen < xOctetString.length || mlen < yOctetString.length) {
|
|
throw new Error("Point coordinate(s) are bigger than the field order.");
|
|
}
|
|
var output = createArray(2 * mlen + 1); // for encoded x and y
|
|
|
|
output[0] = 0x04;
|
|
var offset = mlen - xOctetString.length;
|
|
for (var i = 0; i < xOctetString.length; i++) {
|
|
output[i + 1 + offset] = xOctetString[i];
|
|
}
|
|
offset = mlen - yOctetString.length;
|
|
for (i = 0; i < yOctetString.length; i++) {
|
|
output[mlen + i + 1 + offset] = yOctetString[i];
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
},
|
|
decodePoint: function (encoded, curve) {
|
|
/// <param name="encoded" type="Digits"/>
|
|
/// <param name="curve" type="EllipticCurveFp"/>
|
|
|
|
if (encoded.length < 1) {
|
|
throw new Error("Byte array must have non-zero length");
|
|
}
|
|
|
|
var pOctetString = cryptoMath.digitsToBytes(curve.p);
|
|
var mlen = pOctetString.length;
|
|
|
|
if (encoded[0] === 0x0 && encoded.length === 1) {
|
|
return curve.createPointAtInfinity();
|
|
} else if (encoded[0] === 0x04 && encoded.length === 1 + 2 * mlen) {
|
|
// Standard encoding.
|
|
// Each point is a big endian string of bytes of length.
|
|
// 'ceiling(log_2(Q)/8)'
|
|
// Zero-padded and representing the magnitude of the coordinate.
|
|
var xbytes = createArray(mlen);
|
|
var ybytes = createArray(mlen);
|
|
|
|
for (var i = 0; i < mlen; i++) {
|
|
xbytes[i] = encoded[i + 1];
|
|
ybytes[i] = encoded[mlen + i + 1];
|
|
}
|
|
|
|
var x = cryptoMath.bytesToDigits(xbytes);
|
|
var y = cryptoMath.bytesToDigits(ybytes);
|
|
|
|
return EllipticCurvePointFp(curve, false, x, y);
|
|
} else {
|
|
// We don't support other encoding features such as compression
|
|
throw new Error("Unsupported encoding format");
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
var ModularSquareRootSolver = function (modulus) {
|
|
/// <param name="modulus" type="Digits"/>
|
|
|
|
// The modulus we are going to use.
|
|
var p = modulus;
|
|
|
|
// Special-K not just for breakfast anymore! This is k = (p-3)/4 + 1
|
|
// which is used for NIST curves (or any curve of with P= 3 mod 4).
|
|
// This field is null if p is not of the special form, or k if it is.
|
|
var specialK = [];
|
|
|
|
if (typeof modulus === "undefined") {
|
|
throw new Error("modulus");
|
|
}
|
|
|
|
// Support for odd moduli, only.
|
|
if (cryptoMath.isEven(modulus)) {
|
|
throw new Error("Only odd moduli are supported");
|
|
}
|
|
|
|
// A montgomery multiplier object for doing fast squaring.
|
|
var mul = cryptoMath.MontgomeryMultiplier(p);
|
|
|
|
// 'p === 3 mod 4' then we can use the special super fast version.
|
|
// Otherwise we must use the slower general case algorithm.
|
|
if (p[0] % 4 === 3) {
|
|
// 'special k = (p + 1) / 4'
|
|
cryptoMath.add(p, cryptoMath.One, specialK);
|
|
cryptoMath.shiftRight(specialK, specialK, 2);
|
|
} else {
|
|
specialK = null;
|
|
}
|
|
|
|
// Temp storage
|
|
var temp0 = new Array(p.length);
|
|
var temp1 = new Array(p.length);
|
|
|
|
function squareRootNistCurves(a) {
|
|
/// <summary>Given a number a, returns a solution x to x^2 = a (mod p).</summary>
|
|
/// <param name="a" type="Array">An integer a.</param>
|
|
/// <returns type="Array">The square root of the number a modulo p, if it exists,
|
|
/// otherwise null.</returns>
|
|
|
|
// beta = a^k mod n where k=(n+1)/4 for n == 3 mod 4, thus a^(1/2) mod n
|
|
var beta = cryptoMath.intToDigits(0, 16);
|
|
mul.modExp(a, specialK, beta);
|
|
|
|
// Okay now we gotta double check by squaring.
|
|
var aPrime = [0];
|
|
cryptoMath.modMul(beta, beta, mul.m, aPrime);
|
|
|
|
// If a != x^2 then a has no square root
|
|
if (cryptoMath.compareDigits(a, aPrime) !== 0) {
|
|
return null;
|
|
}
|
|
|
|
return beta;
|
|
}
|
|
|
|
var publicMethods = {
|
|
|
|
squareRoot: function (a) {
|
|
if (specialK !== null) {
|
|
// Use the special case fast code
|
|
return squareRootNistCurves(a);
|
|
} else {
|
|
// Use the general case code
|
|
throw new Error("GeneralCase not supported.");
|
|
}
|
|
},
|
|
|
|
// Given an integer a, this routine returns the Jacobi symbol (a/p),
|
|
// where p is the modulus given in the constructor, which for p an
|
|
// odd prime is also the Legendre symbol. From "Prime Numbers, A
|
|
// Computational Perspective" by Crandall and Pomerance, alg. 2.3.5.
|
|
// The Legendre symbol is defined as:
|
|
// 0 if a === 0 mod p.
|
|
// 1 if a is a quadratic residue (mod p).
|
|
// -1 if a is a quadratic non-reside (mod p).
|
|
jacobiSymbol: function (a) {
|
|
/// <param name="a">An integer a.</param>
|
|
|
|
var modEightMask = 0x7,
|
|
modFourMask = 0x3,
|
|
aPrime,
|
|
pPrime;
|
|
|
|
// Clone our inputs, we are going to destroy them
|
|
aPrime = a.slice();
|
|
pPrime = p.slice();
|
|
|
|
// 'a = a mod p'.
|
|
cryptoMath.reduce(aPrime, pPrime, aPrime, temp0, temp1);
|
|
|
|
// 't = 1'
|
|
var t = 1;
|
|
|
|
// While (a != 0)
|
|
while (!cryptoMath.isZero(aPrime)) {
|
|
// While a is even
|
|
while (cryptoMath.isEven(aPrime)) {
|
|
// 'a <- a / 2'
|
|
cryptoMath.shiftRight(aPrime, aPrime);
|
|
|
|
// If (p mod 8 in {3,5}) t = -t;
|
|
var pMod8 = (pPrime[0] & modEightMask);
|
|
if (pMod8 === 3 || pMod8 === 5) {
|
|
t = -t;
|
|
}
|
|
}
|
|
|
|
// Swap variables
|
|
// (a, p) = (p, a).
|
|
var tmp = aPrime;
|
|
aPrime = pPrime;
|
|
pPrime = tmp;
|
|
|
|
// If (a === p === 3 (mod 4)) t = -t;
|
|
var aMod4 = (aPrime[0] & modFourMask);
|
|
var pMod4 = (pPrime[0] & modFourMask);
|
|
if (aMod4 === 3 && pMod4 === 3) {
|
|
t = -t;
|
|
}
|
|
|
|
// 'a = a mod p'
|
|
cryptoMath.reduce(aPrime, pPrime, aPrime, temp0, temp1);
|
|
}
|
|
|
|
// If (p == 1) return t else return 0
|
|
if (cryptoMath.compareDigits(pPrime, cryptoMath.One) === 0) {
|
|
return t;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
return publicMethods;
|
|
};
|
|
|
|
var curvesInternal = {};
|
|
|
|
var createCurve = function (curveName) {
|
|
|
|
var curveData = curvesInternal[curveName.toUpperCase()];
|
|
|
|
if (!curveData) {
|
|
throw new Error(curveName + " Unsupported curve.");
|
|
}
|
|
|
|
if (curveData.type === 0) {
|
|
return createWeierstrassCurve(curveData);
|
|
}
|
|
|
|
if (curveData.type === 1) {
|
|
return createTedCurve(curveData);
|
|
}
|
|
|
|
throw new Error(curveName + " Unsupported curve type.");
|
|
};
|
|
|
|
return {
|
|
createCurve: createCurve,
|
|
curves: curvesInternal,
|
|
sec1EncodingFp: sec1EncodingFp,
|
|
EllipticCurvePointFp: EllipticCurvePointFp,
|
|
EllipticCurveOperatorFp: EllipticCurveOperatorFp,
|
|
ModularSquareRootSolver: ModularSquareRootSolver
|
|
};
|
|
}
|
|
|
|
var cryptoECC = cryptoECC || MsrcryptoECC();
|
|
|
|
var curve_P256 = {
|
|
name: "P-256",
|
|
type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
p: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
|
|
a: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC],
|
|
b: [0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3, 0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27, 0xD2, 0x60, 0x4B],
|
|
order: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51],
|
|
gx: [0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96],
|
|
gy: [0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5],
|
|
cf: 1 // co-factor
|
|
};
|
|
|
|
var curve_P384 = {
|
|
name: "P-384",
|
|
type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
p: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
|
|
a: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC],
|
|
b: [0xB3, 0x31, 0x2F, 0xA7, 0xE2, 0x3E, 0xE7, 0xE4, 0x98, 0x8E, 0x05, 0x6B, 0xE3, 0xF8, 0x2D, 0x19, 0x18, 0x1D, 0x9C, 0x6E, 0xFE, 0x81, 0x41, 0x12, 0x03, 0x14, 0x08, 0x8F, 0x50, 0x13, 0x87, 0x5A, 0xC6, 0x56, 0x39, 0x8D, 0x8A, 0x2E, 0xD1, 0x9D, 0x2A, 0x85, 0xC8, 0xED, 0xD3, 0xEC, 0x2A, 0xEF],
|
|
order: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF, 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73],
|
|
gx: [0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74, 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98, 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38, 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C, 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7],
|
|
gy: [0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F, 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29, 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C, 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0, 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D, 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F],
|
|
cf: 1 // co-factor
|
|
};
|
|
|
|
var curve_P521 = {
|
|
name: "P-521",
|
|
type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
p: [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
|
|
a: [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC],
|
|
b: [0x00, 0x51, 0x95, 0x3E, 0xB9, 0x61, 0x8E, 0x1C, 0x9A, 0x1F, 0x92, 0x9A, 0x21, 0xA0, 0xB6, 0x85, 0x40, 0xEE, 0xA2, 0xDA, 0x72, 0x5B, 0x99, 0xB3, 0x15, 0xF3, 0xB8, 0xB4, 0x89, 0x91, 0x8E, 0xF1, 0x09, 0xE1, 0x56, 0x19, 0x39, 0x51, 0xEC, 0x7E, 0x93, 0x7B, 0x16, 0x52, 0xC0, 0xBD, 0x3B, 0xB1, 0xBF, 0x07, 0x35, 0x73, 0xDF, 0x88, 0x3D, 0x2C, 0x34, 0xF1, 0xEF, 0x45, 0x1F, 0xD4, 0x6B, 0x50, 0x3F, 0x00],
|
|
order: [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x51, 0x86, 0x87, 0x83, 0xBF, 0x2F, 0x96, 0x6B, 0x7F, 0xCC, 0x01, 0x48, 0xF7, 0x09, 0xA5, 0xD0, 0x3B, 0xB5, 0xC9, 0xB8, 0x89, 0x9C, 0x47, 0xAE, 0xBB, 0x6F, 0xB7, 0x1E, 0x91, 0x38, 0x64, 0x09],
|
|
gx: [0x00, 0xC6, 0x85, 0x8E, 0x06, 0xB7, 0x04, 0x04, 0xE9, 0xCD, 0x9E, 0x3E, 0xCB, 0x66, 0x23, 0x95, 0xB4, 0x42, 0x9C, 0x64, 0x81, 0x39, 0x05, 0x3F, 0xB5, 0x21, 0xF8, 0x28, 0xAF, 0x60, 0x6B, 0x4D, 0x3D, 0xBA, 0xA1, 0x4B, 0x5E, 0x77, 0xEF, 0xE7, 0x59, 0x28, 0xFE, 0x1D, 0xC1, 0x27, 0xA2, 0xFF, 0xA8, 0xDE, 0x33, 0x48, 0xB3, 0xC1, 0x85, 0x6A, 0x42, 0x9B, 0xF9, 0x7E, 0x7E, 0x31, 0xC2, 0xE5, 0xBD, 0x66],
|
|
gy: [0x01, 0x18, 0x39, 0x29, 0x6A, 0x78, 0x9A, 0x3B, 0xC0, 0x04, 0x5C, 0x8A, 0x5F, 0xB4, 0x2C, 0x7D, 0x1B, 0xD9, 0x98, 0xF5, 0x44, 0x49, 0x57, 0x9B, 0x44, 0x68, 0x17, 0xAF, 0xBD, 0x17, 0x27, 0x3E, 0x66, 0x2C, 0x97, 0xEE, 0x72, 0x99, 0x5E, 0xF4, 0x26, 0x40, 0xC5, 0x50, 0xB9, 0x01, 0x3F, 0xAD, 0x07, 0x61, 0x35, 0x3C, 0x70, 0x86, 0xA2, 0x72, 0xC2, 0x40, 0x88, 0xBE, 0x94, 0x76, 0x9F, 0xD1, 0x66, 0x50],
|
|
cf: 1 // co-factor
|
|
};
|
|
|
|
if (typeof cryptoECC !== 'undefined') {
|
|
// Add curves to ECC object
|
|
cryptoECC.curves["P-256"] = curve_P256;
|
|
cryptoECC.curves["P-384"] = curve_P384;
|
|
cryptoECC.curves["P-521"] = curve_P521;
|
|
}
|
|
|
|
var curve_BN254 = {
|
|
name: "BN-254",
|
|
type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
p: [0x25, 0x23, 0x64, 0x82, 0x40, 0x00, 0x00, 0x01, 0xBA, 0x34, 0x4D, 0x80, 0x00, 0x00, 0x00, 0x08, 0x61, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13],
|
|
a: [0x00],
|
|
b: [0x02],
|
|
order: [0x25, 0x23, 0x64, 0x82, 0x40, 0x00, 0x00, 0x01, 0xBA, 0x34, 0x4D, 0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x9F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D],
|
|
gx: [0x25, 0x23, 0x64, 0x82, 0x40, 0x00, 0x00, 0x01, 0xBA, 0x34, 0x4D, 0x80, 0x00, 0x00, 0x00, 0x08, 0x61, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12],
|
|
gy: [0x01],
|
|
cf: 1 // co-factor
|
|
};
|
|
|
|
if (typeof cryptoECC !== 'undefined') {
|
|
// Add curves to ECC object
|
|
cryptoECC.curves["BN-254"] = curve_BN254;
|
|
}
|
|
|
|
//
|
|
// "numsp256d1": Weierstrass curve a=-3, E: y^2 = x^3 - 3x + 152961, p = 2^256-189
|
|
//
|
|
var curve_numsp256d1 = {
|
|
// Curve ID, 2 x targeted security level, order bitlength, prime bitlength
|
|
info: ['numsp256d1', 256, 256, 256],
|
|
// Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
type: 0,
|
|
// Prime p = 2^256-189
|
|
p: [0x43, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "a"
|
|
a: [0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "b"
|
|
b: [0x81, 0x55, 0x02].reverse(),
|
|
// Order of the group
|
|
order: [0x25, 0xa8, 0x51, 0x47, 0x29, 0x20, 0xab, 0x20, 0x60, 0x5c, 0x26, 0xea, 0x75, 0x82, 0x3c, 0xe4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].reverse(),
|
|
// x(generator)
|
|
gx: [0xB1, 0xAC, 0x1A, 0xB2, 0x1E, 0xEE, 0x52, 0xBC, 0x3A, 0xC7, 0xD4, 0x03, 0x09, 0x9B, 0x57, 0x83, 0x09, 0xCB, 0x42, 0x4F, 0xA0, 0x95, 0x7A, 0x29, 0x61, 0xDB, 0xAA, 0x5A, 0xB6, 0xD6, 0x9E, 0xBC].reverse(),
|
|
// y(generator)
|
|
gy: [0x9F, 0xDE, 0x84, 0x21, 0xCB, 0xB9, 0xB5, 0x80, 0xBB, 0x0F, 0x31, 0x15, 0xD1, 0xC3, 0x55, 0xC9, 0x35, 0xE0, 0x04, 0x7E, 0xF7, 0x8B, 0x44, 0x73, 0xA6, 0xB6, 0x99, 0x33, 0xF1, 0xC0, 0x8F, 0xD0].reverse(),
|
|
// co-factor
|
|
cf: 1
|
|
};
|
|
|
|
//
|
|
// "numsp256t1": twisted Edwards curve a=1, E: x^2 + y^2 = 1 - 15342x^2y^2, p = 2^256-189
|
|
//
|
|
var curve_numsp256t1 = {
|
|
// Curve ID, 2 x targeted security level, order bitlength, prime bitlength
|
|
info: ['numsp256t1', 256, 255, 256],
|
|
// Name
|
|
name: "numsp256t1",
|
|
// Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
type: 1,
|
|
// Prime p = 2^256-189
|
|
p: [0x43, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "a"
|
|
a: [0x01],
|
|
// Parameter "d"
|
|
d: [0x55, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Order of the subgroup
|
|
order: [0xF5, 0x4A, 0xDD, 0xEE, 0x90, 0xB1, 0x47, 0x1A, 0x9B, 0x43, 0x59, 0x2F, 0xA5, 0x5A, 0x95, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40].reverse(),
|
|
// x(generator)
|
|
gx: [0xDA, 0x13, 0xED, 0x2E, 0x90, 0xC0, 0xDE, 0xA0, 0x86, 0x35, 0x08, 0xE3, 0x0E, 0x8A, 0x39, 0x0C, 0xD6, 0x9B, 0x20, 0x69, 0x5F, 0x3D, 0x1E, 0xCD, 0x7D, 0x23, 0xEA, 0x6A, 0xFB, 0x14, 0x75, 0x8A].reverse(),
|
|
// y(generator)
|
|
gy: [0xE6, 0x89, 0x8A, 0x79, 0xE7, 0x16, 0xA6, 0x2F, 0xD3, 0x6E, 0x85, 0x10, 0xD8, 0x61, 0x5F, 0x71, 0x10, 0x80, 0x4B, 0xA6, 0xD9, 0x65, 0x96, 0xCE, 0xC7, 0x25, 0xD9, 0xD9, 0x9F, 0x3E, 0xD5, 0x44].reverse(),
|
|
// co-factor
|
|
cf: 4
|
|
};
|
|
|
|
//
|
|
// "numsp384d1": Weierstrass curve a=-3, E: y^2 = x^3 - 3x - 34568, p = 2^384-317
|
|
//
|
|
var curve_numsp384d1 = {
|
|
// Curve ID, 2 x targeted security level, order bitlength, prime bitlength
|
|
info: ['numsp384d1', 384, 384, 384],
|
|
// Name
|
|
name: "numsp384d1",
|
|
// Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
type: 0,
|
|
// Prime p = 2^384-317
|
|
p: [0xC3, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "a"
|
|
a: [0xC0, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "b"
|
|
b: [0xBB, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Order of the group
|
|
order: [0xb9, 0x61, 0x0e, 0x7b, 0xf6, 0x81, 0x4d, 0x60, 0x7a, 0xe2, 0x37, 0x4c, 0x3d, 0x9d, 0xda, 0xbe, 0x81, 0x68, 0x5d, 0xeb, 0x1e, 0xaf, 0x1e, 0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].reverse(),
|
|
// x(generator)
|
|
gx: [0x2A, 0x15, 0x98, 0x20, 0x04, 0xBA, 0x9C, 0xEB, 0x7B, 0xC4, 0x61, 0x0F, 0x10, 0xED, 0x2E, 0x52, 0x42, 0xC7, 0x6C, 0x2A, 0x1B, 0x29, 0xBD, 0xF3, 0xF4, 0xF9, 0x81, 0xFB, 0xCD, 0xC1, 0x25, 0x02,
|
|
0xA6, 0xF1, 0x05, 0x41, 0x22, 0xCA, 0x80, 0x48, 0x1C, 0x18, 0x6F, 0xB1, 0xF0, 0x56, 0x79, 0x75].reverse(),
|
|
// y(generator)
|
|
gy: [0x16, 0x07, 0x18, 0x66, 0xEC, 0xB8, 0x74, 0x5C, 0x26, 0xAD, 0xF4, 0xBF, 0xDB, 0xB4, 0xD6, 0xBC, 0x7E, 0x83, 0x1A, 0x12, 0x7D, 0x83, 0x20, 0xB9, 0x9C, 0x73, 0x7F, 0xF8, 0x77, 0x69, 0x04, 0xB0,
|
|
0x7E, 0xCF, 0x84, 0x05, 0x30, 0x3D, 0xE3, 0xD7, 0x38, 0x8E, 0x9B, 0xE1, 0x68, 0xE3, 0xDE, 0xAC].reverse(),
|
|
// co-factor
|
|
cf: 1
|
|
};
|
|
|
|
//
|
|
// "numsp384t1": twisted Edwards curve a=1, E: x^2 + y^2 = 1 - 11556x^2y^2, p = 2^384-317
|
|
//
|
|
var curve_numsp384t1 = {
|
|
// Curve ID, 2 x targeted security level, order bitlength, prime bitlength
|
|
info: ['numsp384t1', 384, 382, 384],
|
|
// Name
|
|
name: "numsp384t1",
|
|
// Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
type: 1,
|
|
// Prime p = 2^2^384-317
|
|
p: [ 0xC3, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ].reverse(),
|
|
// Parameter "a"
|
|
a: [ 0x01 ],
|
|
// Parameter "d"
|
|
d: [ 0x9F, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ].reverse(),
|
|
// Order of the subgroup
|
|
order: [ 0x7D, 0x89, 0xA3, 0xE6, 0xC4, 0xDC, 0xB9, 0x20, 0x79, 0xC8, 0x35, 0xAB, 0x5A, 0x55, 0xE4, 0x61, 0xCF, 0xE1, 0x6B, 0xB4, 0x1C, 0x1A, 0x47, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F ].reverse(),
|
|
// x(generator)
|
|
gx: [ 0xDE, 0x6B, 0x20, 0x6C, 0xE4, 0x40, 0xD5, 0x50, 0x13, 0x94, 0x45, 0x65, 0xB1, 0x92, 0xF2, 0x6F, 0x40, 0x63, 0x31, 0xF3, 0xA8, 0xFF, 0x63, 0x57, 0x00, 0x4C, 0xBE, 0xE5, 0x46, 0xF4, 0x0B, 0xB3,
|
|
0xB5, 0x5D, 0xE5, 0x9A, 0x12, 0xA2, 0xB6, 0xC0, 0x6C, 0x26, 0xA9, 0x45, 0xFB, 0x11, 0xB1, 0x61 ].reverse(),
|
|
// y(generator)
|
|
gy: [ 0x92, 0x93, 0x72, 0xF0, 0xE1, 0x03, 0x8D, 0x9D, 0xDC, 0x48, 0xEC, 0x46, 0xF9, 0xB0, 0x72, 0x00, 0x4B, 0x96, 0x45, 0xF6, 0xF7, 0x98, 0x0F, 0x83, 0x56, 0x5F, 0x42, 0xF1, 0x74, 0x82, 0xAD, 0x16,
|
|
0xD7, 0x0D, 0xB1, 0x23, 0xA4, 0xB1, 0x38, 0x87, 0xB0, 0xEE, 0xA6, 0xB9, 0x67, 0x3E, 0x98, 0x82 ].reverse(),
|
|
// co-factor
|
|
cf: 4
|
|
};
|
|
|
|
//
|
|
// "numsp512d1": Weierstrass curve a=-3, E: y^2 = x^3 - 3x + 121243, p = 2^512-569
|
|
//
|
|
var curve_numsp512d1 = {
|
|
// Curve ID, 2 x targeted security level, order bitlength, prime bitlength
|
|
info: ['numsp512d1', 512, 512, 512],
|
|
// Name
|
|
name: "numsp512d1",
|
|
// Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
type: 0,
|
|
// Prime p = 2^512-569
|
|
p: [0xC7, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "a"
|
|
a: [0xC4, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "b"
|
|
b: [0x9B, 0xD9, 0x01].reverse(),
|
|
// Order of the group
|
|
order: [0x5d, 0x55, 0x33, 0x04, 0x39, 0x3f, 0x15, 0xce, 0x43, 0xd2, 0x7c, 0x60, 0x36, 0x8b, 0x56, 0x3b, 0xc6, 0xbd, 0xd0, 0x97, 0xed, 0x58, 0xc2, 0x4f, 0x1b, 0x83, 0xe7, 0x94, 0xfb, 0xa4, 0x3c, 0x5b,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].reverse(),
|
|
// x(generator)
|
|
gx: [0x57, 0xAE, 0xAB, 0x8C, 0x95, 0x87, 0x82, 0xDC, 0xE2, 0x5D, 0x6F, 0x7D, 0x13, 0x60, 0x5D, 0x1D, 0x83, 0x15, 0x56, 0x25, 0x86, 0x42, 0x79, 0x93, 0x9E, 0x35, 0x6B, 0x07, 0x51, 0xA1, 0x21, 0x50,
|
|
0xF9, 0xD9, 0x06, 0x53, 0xC2, 0xE0, 0x06, 0x45, 0x85, 0xF6, 0x01, 0xB5, 0x3B, 0xD8, 0xCA, 0x98, 0x52, 0x3B, 0x3D, 0xA0, 0x02, 0x70, 0x2B, 0xDA, 0x93, 0x0A, 0x1D, 0x14, 0x47, 0x34, 0xC0, 0x3A].reverse(),
|
|
// y(generator)
|
|
gy: [0xA6, 0x27, 0x35, 0x38, 0x60, 0x87, 0xA0, 0x23, 0xE9, 0x0F, 0xFD, 0x4C, 0x1E, 0x5C, 0x2B, 0xCF, 0x02, 0x56, 0x5A, 0xB2, 0x40, 0xA8, 0x21, 0xC1, 0xE9, 0xED, 0x0E, 0x8B, 0xDA, 0x15, 0x84, 0xA2,
|
|
0x14, 0x4F, 0xD1, 0x7B, 0x0C, 0x26, 0x4B, 0x8F, 0x8C, 0xBB, 0xBC, 0xAB, 0xDE, 0xDB, 0x97, 0x4B, 0x00, 0xB1, 0xEB, 0x63, 0xDC, 0xEE, 0x0E, 0xCE, 0xB3, 0x56, 0xAD, 0x29, 0xCA, 0x54, 0x3A, 0x94].reverse(),
|
|
// co-factor
|
|
cf: 4
|
|
}
|
|
|
|
//
|
|
// "numsp512t1": twisted Edwards curve a=1, E: x^2 + y^2 = 1 - 78296x^2y^2, p = 2^512-569
|
|
//
|
|
var curve_numsp512t1 = {
|
|
// Curve ID, , 2 x targeted security level, order bitlength, prime bitlength
|
|
info: ['numsp512t1', 512, 510, 512],
|
|
// Name
|
|
name: "numsp512t1",
|
|
// Curve Type 0 = Weierstrass, 1 Twisted Edwards
|
|
type: 1,
|
|
// Prime p = 2^512-569
|
|
p: [0xC7, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Parameter "a"
|
|
a: [0x01].reverse(),
|
|
// Parameter "d"
|
|
d: [0xEF, 0xCB, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
|
|
// Order of the subgroup
|
|
order: [0x6D, 0xD4, 0xEE, 0x1B, 0xF5, 0x8C, 0x46, 0x67, 0xFF, 0xEC, 0xEF, 0x6D, 0x78, 0x05, 0x46, 0x2A, 0xF5, 0x86, 0xB6, 0x70, 0xC9, 0xD8, 0x3F, 0x9E, 0xBA, 0x91, 0xCF, 0x2F, 0x6D, 0x63, 0xF0, 0xB4,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F].reverse(),
|
|
// x(generator)
|
|
gx: [0xFE, 0x57, 0xEC, 0x99, 0x29, 0xAB, 0xB9, 0xC5, 0x15, 0xF0, 0xC4, 0x7C, 0x42, 0x25, 0xE5, 0x0F, 0xAD, 0x04, 0x89, 0x56, 0x92, 0xC9, 0xBD, 0x78, 0x0F, 0x73, 0x46, 0xEE, 0x4E, 0xC1, 0x21, 0x46,
|
|
0x47, 0x81, 0x3B, 0x27, 0xBE, 0x7E, 0xA1, 0x27, 0x82, 0xA3, 0xC4, 0x4D, 0x9F, 0xE7, 0xD1, 0x2F, 0x33, 0xC5, 0xD3, 0x88, 0x78, 0xCB, 0x18, 0x7A, 0x9C, 0xB6, 0x8D, 0x12, 0x6D, 0x31, 0x8E, 0xDF].reverse(),
|
|
// y(generator)
|
|
gy: [0xE1, 0xF5, 0xE2, 0xC1, 0xC0, 0xDE, 0x6D, 0x32, 0x1F, 0xD0, 0xF1, 0x9B, 0x8A, 0xD3, 0x66, 0x02, 0xFD, 0xC1, 0xEC, 0x2A, 0x86, 0x06, 0x1A, 0x60, 0x62, 0x35, 0x96, 0xE9, 0xF2, 0x53, 0xCA, 0x20,
|
|
0x41, 0x83, 0x9E, 0x90, 0x95, 0x6B, 0x2B, 0xA9, 0x22, 0x9D, 0x25, 0xD8, 0x26, 0xF7, 0x76, 0xE4, 0x6E, 0x25, 0x2A, 0xA8, 0x77, 0xF5, 0xB0, 0x98, 0x71, 0xCA, 0x49, 0x9D, 0xF3, 0xBF, 0x09, 0x6D].reverse(),
|
|
// co-factor
|
|
cf: 4
|
|
}
|
|
|
|
if (typeof cryptoECC !== 'undefined') {
|
|
// Add curves to ECC object - use uppercase names
|
|
cryptoECC.curves["NUMSP256D1"] = curve_numsp256d1;
|
|
cryptoECC.curves["NUMSP384D1"] = curve_numsp384d1;
|
|
cryptoECC.curves["NUMSP512D1"] = curve_numsp512t1;
|
|
cryptoECC.curves["NUMSP256T1"] = curve_numsp256t1;
|
|
cryptoECC.curves["NUMSP384T1"] = curve_numsp384t1;
|
|
cryptoECC.curves["NUMSP512T1"] = curve_numsp512t1;
|
|
}
|
|
|
|
function BlockFunctionTypeDef(message, blockIndex, initialHashValues, k, w) {
|
|
/// <signature>
|
|
/// <summary>
|
|
/// Type definition for block function
|
|
/// </summary>
|
|
/// <param name="message" type="Array">Block data</param>
|
|
/// <param name="blockIndex" type="Number">Block number to operate on</param>
|
|
/// <param name="initialHashValues" type="Array"></param>
|
|
/// <param name="k" type="Array">K constants</param>
|
|
/// <param name="w" type="Array">Array to hold w values</param>
|
|
/// <returns type="Array">Hash values</returns>
|
|
/// </signature>
|
|
|
|
return [];
|
|
}
|
|
|
|
var msrcryptoSha = function (name, der, h, k, blockBytes, blockFunction, truncateTo) {
|
|
/// <summary>
|
|
/// Returns a hash function using the passed in parameters.
|
|
/// </summary>
|
|
/// <param name="name" type="String">Name of the hash function.</param>
|
|
/// <param name="der" type="Array"></param>
|
|
/// <param name="h" type="Array"></param>
|
|
/// <param name="k" type="Array"></param>
|
|
/// <param name="blockBytes" type="Number">The number of bytes in a block.</param>
|
|
/// <param name="blockFunction" type="BlockFunctionTypeDef">Function for processing blocks.</param>
|
|
/// <param name="truncateTo" type="Number">Truncate the resulting hash to a fixed length.</param>
|
|
/// <returns type="Object"></returns>
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
// Make a copy of h so we don't alter the initialization array.
|
|
var hv = h.slice(),
|
|
w = new Array(blockBytes),
|
|
buffer = [],
|
|
blocksProcessed = 0;
|
|
|
|
function hashBlocks(message) {
|
|
/// <summary>
|
|
/// Breaks a array of data into full blocks and hashes each block in sequence.
|
|
/// Data at the end of the message that does not fill an entire block is
|
|
/// returned.
|
|
/// </summary>
|
|
/// <param name="message" type="Array">Byte data to hash</param>
|
|
/// <returns type="Array">Unprocessed data at the end of the message that did
|
|
/// not fill an entire block.</returns>
|
|
|
|
var blockCount = Math.floor(message.length / blockBytes);
|
|
|
|
// Process each block of the message
|
|
for (var block = 0; block < blockCount; block++) {
|
|
blockFunction(message, block, hv, k, w);
|
|
}
|
|
|
|
// Keep track of the number of blocks processed.
|
|
// We have to put the total message size into the padding.
|
|
blocksProcessed += blockCount;
|
|
|
|
// Return the unprocessed data.
|
|
return message.slice(blockCount * blockBytes);
|
|
}
|
|
|
|
function hashToBytes() {
|
|
/// <summary>
|
|
/// Converts stored hash values (32-bit ints) to bytes.
|
|
/// </summary>
|
|
/// <returns type="Array"></returns>
|
|
|
|
// Move the results to an uint8 array.
|
|
var hash = [];
|
|
|
|
for (var i = 0; i < hv.length; i++) {
|
|
hash = hash.concat(utils.int32ToBytes(hv[i]));
|
|
}
|
|
|
|
// Truncate the results depending on the hash algorithm used.
|
|
hash.length = (truncateTo / 8);
|
|
|
|
return hash;
|
|
}
|
|
|
|
function addPadding(messageBytes) {
|
|
/// <summary>
|
|
/// Builds and appends padding to a message
|
|
/// </summary>
|
|
/// <param name="messageBytes" type="Array">Message to pad</param>
|
|
/// <returns type="Array">The message array + padding</returns>
|
|
|
|
var padLen = blockBytes - messageBytes.length % blockBytes;
|
|
|
|
// If there is 8 (16 for sha-512) or less bytes of padding, pad an additional block.
|
|
(padLen <= (blockBytes / 8)) && (padLen += blockBytes);
|
|
|
|
// Create a new Array that will contain the message + padding
|
|
var padding = utils.getVector(padLen);
|
|
|
|
// Set the 1 bit at the end of the message data
|
|
padding[0] = 128;
|
|
|
|
// Set the length equal to the previous data len + the new data len
|
|
var messageLenBits = (messageBytes.length + blocksProcessed * blockBytes) * 8;
|
|
|
|
// Set the message length in the last 4 bytes
|
|
padding[padLen - 4] = messageLenBits >>> 24 & 255;
|
|
padding[padLen - 3] = messageLenBits >>> 16 & 255;
|
|
padding[padLen - 2] = messageLenBits >>> 8 & 255;
|
|
padding[padLen - 1] = messageLenBits & 255;
|
|
|
|
return messageBytes.concat(padding);
|
|
}
|
|
|
|
function computeHash(messageBytes) {
|
|
/// <summary>
|
|
/// Computes the hash of an entire message.
|
|
/// </summary>
|
|
/// <param name="messageBytes" type="Array">Byte array to hash</param>
|
|
/// <returns type="Array">Hash of message bytes</returns>
|
|
|
|
buffer = hashBlocks(messageBytes);
|
|
|
|
return finish();
|
|
}
|
|
|
|
function process(messageBytes) {
|
|
/// <summary>
|
|
/// Call process repeatedly to hash a stream of bytes. Then call 'finish' to
|
|
/// complete the hash and get the final result.
|
|
/// </summary>
|
|
/// <param name="messageBytes" type="Array"></param>
|
|
|
|
// Append the new data to the buffer (previous unprocessed data)
|
|
buffer = buffer.concat(messageBytes);
|
|
|
|
// If there is at least one block of data, hash it
|
|
if (buffer.length >= blockBytes) {
|
|
|
|
// The remaining unprocessed data goes back into the buffer
|
|
buffer = hashBlocks(buffer);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
function finish() {
|
|
/// <summary>
|
|
/// Called after one or more calls to process. This will finalize the hashing
|
|
/// of the 'streamed' data and return the hash.
|
|
/// </summary>
|
|
/// <returns type="Array">Hash of message bytes</returns>
|
|
|
|
// All the full blocks of data have been processed. Now we pad the rest and hash.
|
|
// Buffer should be empty now.
|
|
if (hashBlocks(addPadding(buffer)).length !== 0) {
|
|
throw new Error("buffer.length !== 0");
|
|
}
|
|
|
|
// Convert the intermediate hash values to bytes
|
|
var result = hashToBytes();
|
|
|
|
// Reset the buffer
|
|
buffer = [];
|
|
|
|
// Restore the initial hash values
|
|
hv = h.slice();
|
|
|
|
// Reset the block counter
|
|
blocksProcessed = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
return {
|
|
name: name,
|
|
computeHash: computeHash,
|
|
process: process,
|
|
finish: finish,
|
|
der: der,
|
|
hashLen: truncateTo,
|
|
maxMessageSize: 0xFFFFFFFF // (2^32 - 1 is max array size in JavaScript)
|
|
};
|
|
|
|
};
|
|
|
|
/// JS2025.InsertSpaceBeforeCommentText,
|
|
/// JS2027.PunctuateCommentsCorrectly,
|
|
/// JS2005.UseShortFormInitializations,
|
|
/// JS3056.DeclareVariablesOnceOnly,
|
|
/// JS2073.CommentIsMisspelled,
|
|
/// JS3053.IncorrectNumberOfArguments,
|
|
/// JS3058.DeclareVariablesBeforeUse,
|
|
/// JS2074.IdentifierNameIsMisspelled,
|
|
/// JS3055.InconsistentReturnType
|
|
/// </disable>
|
|
|
|
var msrcryptoSha1 = (function () {
|
|
|
|
function hashBlock(message, blockIndex, hv, k, w) {
|
|
/// <summary>
|
|
/// Block function for hashing algorithm to use.
|
|
/// </summary>
|
|
/// <param name="message" type="Array">Block data to hash</param>
|
|
/// <param name="blockIndex" type="Number">The block of the data to hash</param>
|
|
/// <param name="hv" type="Array">Initial hash values</param>
|
|
/// <param name="k" type="Array">K constants</param>
|
|
/// <param name="w" type="Array">Buffer for w values</param>
|
|
/// <returns type="Array">Updated initial hash values</returns>
|
|
|
|
var t, i, temp, x0, blockSize = 64, mask = 0xFFFFFFFF;
|
|
|
|
var ra = hv[0],
|
|
rb = hv[1],
|
|
rc = hv[2],
|
|
rd = hv[3],
|
|
re = hv[4];
|
|
|
|
// 0 ≤ t ≤ 15
|
|
for (i = 0; i < 16; i++) {
|
|
w[i] = utils.bytesToInt32(message, blockIndex * blockSize + i * 4);
|
|
}
|
|
|
|
// 16 ≤ t ≤ 79
|
|
for (t = 16; t < 80; t++) {
|
|
x0 = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
|
|
w[t] = (x0 << 1) | (x0 >>> 31);
|
|
}
|
|
|
|
for (i = 0; i < 80; i++) {
|
|
|
|
// Ch(x, y, z)=(x & y) ^ (~x & z)
|
|
temp = ((ra << 5) | (ra >>> 27));
|
|
|
|
temp +=
|
|
i >= 60 ? (rb ^ rc ^ rd) :
|
|
i >= 40 ? ((rb & rc) ^ (rb & rd) ^ (rc & rd)) :
|
|
i >= 20 ? (rb ^ rc ^ rd) :
|
|
/*i<=20*/ ((rb & rc) ^ ((~rb) & rd));
|
|
|
|
temp += (re + k[i] + w[i]);
|
|
|
|
re = rd;
|
|
rd = rc;
|
|
rc = ((rb << 30) | (rb >>> 2));
|
|
rb = ra;
|
|
ra = temp;
|
|
}
|
|
|
|
// Update the hash values
|
|
hv[0] += ra & mask;
|
|
hv[1] += rb & mask;
|
|
hv[2] += rc & mask;
|
|
hv[3] += rd & mask;
|
|
hv[4] += re & mask;
|
|
|
|
return hv;
|
|
}
|
|
|
|
var utils = msrcryptoUtilities,
|
|
upd = utils.unpackData,
|
|
h = upd("Z0UjAe/Nq4mYutz+EDJUdsPS4fA=", 4, 1),
|
|
k = upd("WoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroY8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdY", 4, 1),
|
|
der = upd("MCEwCQYFKw4DAhoFAAQU");
|
|
|
|
return {
|
|
sha1: msrcryptoSha("SHA-1", der, h, k, 64, hashBlock, 160)
|
|
};
|
|
|
|
})();
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoSha1.hash = function ( p) {
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoSha1.sha1.process(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoSha1.sha1.finish();
|
|
}
|
|
|
|
return msrcryptoSha1.sha1.computeHash(p.buffer);
|
|
|
|
};
|
|
|
|
operations.register("digest", "sha-1", msrcryptoSha1.hash);
|
|
|
|
}
|
|
|
|
msrcryptoHashFunctions["sha-1"] = msrcryptoSha1.sha1;
|
|
|
|
var msrcryptoSha256 = (function () {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
function hashBlock(message, blockIndex, hv, k, w) {
|
|
/// <summary>
|
|
/// Block function for hashing algorithm to use.
|
|
/// </summary>
|
|
/// <param name="message" type="Array">Block data to hash</param>
|
|
/// <param name="blockIndex" type="Number">The block of the data to hash</param>
|
|
/// <param name="hv" type="Array">Initial hash values</param>
|
|
/// <param name="k" type="Array">K constants</param>
|
|
/// <param name="w" type="Array">Buffer for w values</param>
|
|
/// <returns type="Array">Updated initial hash values</returns>
|
|
|
|
var t, i, temp, x0, x1, blockSize = 64, mask = 0xFFFFFFFF;
|
|
|
|
var ra = hv[0],
|
|
rb = hv[1],
|
|
rc = hv[2],
|
|
rd = hv[3],
|
|
re = hv[4],
|
|
rf = hv[5],
|
|
rg = hv[6],
|
|
rh = hv[7];
|
|
|
|
// 0 ≤ t ≤ 15
|
|
for (i = 0; i < 16; i++) {
|
|
w[i] = utils.bytesToInt32(message, blockIndex * blockSize + i * 4);
|
|
}
|
|
|
|
// 16 ≤ t ≤ 63
|
|
for (t = 16; t < 64; t++) {
|
|
|
|
x0 = w[t - 15];
|
|
x1 = w[t - 2];
|
|
|
|
w[t] = (((x1 >>> 17) | (x1 << 15)) ^ ((x1 >>> 19) | (x1 << 13)) ^ (x1 >>> 10))
|
|
+ w[t - 7]
|
|
+ (((x0 >>> 7) | (x0 << 25)) ^ ((x0 >>> 18) | (x0 << 14)) ^ (x0 >>> 3))
|
|
+ w[t - 16];
|
|
|
|
w[t] = w[t] & mask;
|
|
}
|
|
|
|
for (i = 0; i < 64; i++) {
|
|
|
|
temp = rh +
|
|
((re >>> 6 | re << 26) ^ (re >>> 11 | re << 21) ^ (re >>> 25 | re << 7)) +
|
|
((re & rf) ^ ((~re) & rg)) +
|
|
k[i] + w[i];
|
|
|
|
rd += temp;
|
|
|
|
temp += ((ra >>> 2 | ra << 30) ^ (ra >>> 13 | ra << 19) ^ (ra >>> 22 | ra << 10)) +
|
|
((ra & (rb ^ rc)) ^ (rb & rc));
|
|
|
|
rh = rg; // 'h' = g
|
|
rg = rf; // 'g' = f
|
|
rf = re; // 'f' = e
|
|
re = rd; // 'e' = d
|
|
rd = rc; // 'd' = c
|
|
rc = rb; // 'c' = b
|
|
rb = ra; // 'b' = a
|
|
ra = temp; // 'a' = temp
|
|
|
|
}
|
|
|
|
// Update the hash values
|
|
hv[0] += ra & mask;
|
|
hv[1] += rb & mask;
|
|
hv[2] += rc & mask;
|
|
hv[3] += rd & mask;
|
|
hv[4] += re & mask;
|
|
hv[5] += rf & mask;
|
|
hv[6] += rg & mask;
|
|
hv[7] += rh & mask;
|
|
|
|
return hv;
|
|
}
|
|
|
|
var k256, h224, h256, der224, der256, upd = utils.unpackData;
|
|
|
|
h224 = upd("wQWe2DZ81QcwcN0X9w5ZOf/ACzFoWBURZPmPp776T6Q", 4, 1);
|
|
|
|
h256 = upd("agnmZ7tnroU8bvNypU/1OlEOUn+bBWiMH4PZq1vgzRk", 4, 1);
|
|
|
|
k256 = upd("QoovmHE3RJG1wPvP6bXbpTlWwltZ8RHxkj+CpKscXtXYB6qYEoNbASQxhb5VDH3Dcr5ddIDesf6b3AanwZvxdOSbacHvvkeGD8GdxiQMocwt6SxvSnSEqlywqdx2+YjamD5RUqgxxm2wAyfIv1l/x8bgC/PVp5FHBspjURQpKWcntwqFLhshOE0sbfxTOA0TZQpzVHZqCruBwskuknIshaK/6KGoGmZLwkuLcMdsUaPRkugZ1pkGJPQONYUQaqBwGaTBFh43bAgnSHdMNLC8tTkcDLNO2KpKW5zKT2gub/N0j4LueKVjb4TIeBSMxwIIkL7/+qRQbOu++aP3xnF48g", 4, 1);
|
|
|
|
// SHA-224 DER encoding
|
|
// 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C
|
|
der224 = upd("MC0wDQYJYIZIAWUDBAIEBQAEHA");
|
|
|
|
// SHA-256 DER encoding
|
|
// 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20
|
|
der256 = upd("MDEwDQYJYIZIAWUDBAIBBQAEIA");
|
|
|
|
return {
|
|
sha224: msrcryptoSha("SHA-224", der224, h224, k256, 64, hashBlock, 224),
|
|
sha256: msrcryptoSha("SHA-256", der256, h256, k256, 64, hashBlock, 256)
|
|
};
|
|
})();
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoSha256.hash256 = function ( p) {
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoSha256.sha256.process(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoSha256.sha256.finish();
|
|
}
|
|
|
|
return msrcryptoSha256.sha256.computeHash(p.buffer);
|
|
|
|
};
|
|
|
|
msrcryptoSha256.hash224 = function ( p) {
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoSha256.sha224.process(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoSha256.sha224.finish();
|
|
}
|
|
|
|
return msrcryptoSha256.sha224.computeHash(p.buffer);
|
|
|
|
};
|
|
|
|
operations.register("digest", "sha-224", msrcryptoSha256.hash224);
|
|
operations.register("digest", "sha-256", msrcryptoSha256.hash256);
|
|
}
|
|
|
|
msrcryptoHashFunctions["sha-224"] = msrcryptoSha256.sha224;
|
|
msrcryptoHashFunctions["sha-256"] = msrcryptoSha256.sha256;
|
|
|
|
var msrcryptoSha512 = (function () {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
function add(x0, x1, y0, y1, resultArray) {
|
|
|
|
// The sum here may result in a number larger than 32-bits.
|
|
// Or-ing with zero forces to a 32-bit signed internal state
|
|
// and a truncation to a 32-bit number;
|
|
var lowSum = (x1 + y1) | 0;
|
|
|
|
// If lowSum is less than either parameter (x1 or y1), we know we overflowed
|
|
// and a carry will need to be added to the high order bits.
|
|
// The 32-bit integer is signed. So large numbers can flip to negative values.
|
|
// The zero-shift pulls the number back out of the 32-bit state so we know
|
|
// we're comparing positive values.
|
|
var carry = (lowSum >>> 0 < y1 >>> 0);
|
|
|
|
resultArray[0] = (x0 + y0 + carry) | 0;
|
|
resultArray[1] = lowSum;
|
|
|
|
return;
|
|
}
|
|
|
|
function hashBlock(message, blockIndex, hv, k, w) {
|
|
/// <summary>
|
|
/// Block function for hashing algorithm to use.
|
|
/// </summary>
|
|
/// <param name="message" type="Array">Block data to hash</param>
|
|
/// <param name="blockIndex" type="Number">The block of the data to hash</param>
|
|
/// <param name="hv" type="Array">Initial hash values</param>
|
|
/// <param name="k" type="Array">K constants</param>
|
|
/// <param name="w" type="Array">Buffer for w values</param>
|
|
/// <returns type="Array">Updated initial hash values</returns>
|
|
|
|
var t, i, blockBytes = 128,
|
|
tah, tal, tbh, tbl, xh, xl,
|
|
tc = [], td = [], te = [], index;
|
|
|
|
var ah = hv[0], al = hv[1],
|
|
bh = hv[2], bl = hv[3],
|
|
ch = hv[4], cl = hv[5],
|
|
dh = hv[6], dl = hv[7],
|
|
eh = hv[8], el = hv[9],
|
|
fh = hv[10], fl = hv[11],
|
|
gh = hv[12], gl = hv[13],
|
|
hh = hv[14], hl = hv[15];
|
|
|
|
for (t = 0; t < 32; t++) {
|
|
index = blockIndex * blockBytes + t * 4;
|
|
w[t] = message.slice(index, index + 4);
|
|
w[t] = (w[t][0] << 24) | (w[t][1] << 16) | (w[t][2] << 8) | w[t][3];
|
|
}
|
|
|
|
// 16 ≤ t ≤ 80
|
|
for (t = 32; t < 160; t += 2) {
|
|
|
|
xh = w[t - 30];
|
|
xl = w[t - 29];
|
|
|
|
tah = (xh >>> 1 | xl << 31) ^ (xh >>> 8 | xl << 24) ^ (xh >>> 7);
|
|
tal = (xl >>> 1 | xh << 31) ^ (xl >>> 8 | xh << 24) ^ (xl >>> 7 | xh << 25);
|
|
|
|
xh = w[t - 4];
|
|
xl = w[t - 3];
|
|
|
|
tbh = (xh >>> 19 | xl << 13) ^ (xl >>> 29 | xh << 3) ^ (xh >>> 6);
|
|
tbl = (xl >>> 19 | xh << 13) ^ (xh >>> 29 | xl << 3) ^ (xl >>> 6 | xh << 26);
|
|
|
|
add(tbh, tbl, w[t - 14], w[t - 13], tc);
|
|
|
|
add(tah, tal, tc[0], tc[1], tc);
|
|
|
|
add(w[t - 32], w[t - 31], tc[0], tc[1], tc);
|
|
|
|
w[t] = tc[0]; w[t + 1] = tc[1];
|
|
}
|
|
|
|
for (i = 0; i < 160; i += 2) {
|
|
|
|
// S1 =======================================================================
|
|
tah = (eh >>> 14 | el << 18) ^ (eh >>> 18 | el << 14) ^ (el >>> 9 | eh << 23);
|
|
tal = (el >>> 14 | eh << 18) ^ (el >>> 18 | eh << 14) ^ (eh >>> 9 | el << 23);
|
|
|
|
// Ch
|
|
tbh = (eh & fh) ^ (gh & ~eh);
|
|
tbl = (el & fl) ^ (gl & ~el);
|
|
|
|
// C = h + S1
|
|
add(hh, hl, tah, tal, tc);
|
|
|
|
// D = ch + kConstants-i
|
|
add(tbh, tbl, k[i], k[i + 1], td);
|
|
|
|
// E = w[i] + C
|
|
add(tc[0], tc[1], w[i], w[i + 1], te);
|
|
|
|
// E = E + D TEMP
|
|
add(td[0], td[1], te[0], te[1], te);
|
|
|
|
// D = C + E
|
|
add(te[0], te[1], dh, dl, tc);
|
|
dh = tc[0]; dl = tc[1];
|
|
|
|
// S0
|
|
tal = (al >>> 28 | ah << 4) ^ (ah >>> 2 | al << 30) ^ (ah >>> 7 | al << 25);
|
|
tah = (ah >>> 28 | al << 4) ^ (al >>> 2 | ah << 30) ^ (al >>> 7 | ah << 25);
|
|
|
|
tbl = (al & (bl ^ cl)) ^ (bl & cl);
|
|
tbh = (ah & (bh ^ ch)) ^ (bh & ch);
|
|
|
|
// S0 + maj
|
|
add(te[0], te[1], tah, tal, tc);
|
|
tah = tc[0]; tal = tc[1];
|
|
|
|
// 'temp' = temp + (S0 + maj)
|
|
add(tbh, tbl, tah, tal, tc);
|
|
tah = tc[0]; tal = tc[1];
|
|
|
|
hh = gh;
|
|
hl = gl; // 'h' = g
|
|
gh = fh;
|
|
gl = fl; // 'g' = f
|
|
fh = eh;
|
|
fl = el; // 'f' = e
|
|
eh = dh;
|
|
el = dl; // 'e' = d
|
|
dh = ch;
|
|
dl = cl; // 'd' = c
|
|
ch = bh;
|
|
cl = bl; // 'c' = b
|
|
bh = ah;
|
|
bl = al; // 'b' = a
|
|
ah = tah;
|
|
al = tal; // 'a' = temp
|
|
}
|
|
|
|
// This is how you would add without calling add()
|
|
// hv[1] = ((hv[1] + al) | 0) >>> 0;
|
|
// hv[0] = hv[0] + ah + (hv[1] < al >>> 0) | 0;
|
|
|
|
add(hv[0], hv[1], ah, al, tc);
|
|
hv[0] = tc[0]; hv[1] = tc[1];
|
|
|
|
add(hv[2], hv[3], bh, bl, tc);
|
|
hv[2] = tc[0]; hv[3] = tc[1];
|
|
|
|
add(hv[4], hv[5], ch, cl, tc);
|
|
hv[4] = tc[0]; hv[5] = tc[1];
|
|
|
|
add(hv[6], hv[7], dh, dl, tc);
|
|
hv[6] = tc[0]; hv[7] = tc[1];
|
|
|
|
add(hv[8], hv[9], eh, el, tc);
|
|
hv[8] = tc[0]; hv[9] = tc[1];
|
|
|
|
add(hv[10], hv[11], fh, fl, tc);
|
|
hv[10] = tc[0]; hv[11] = tc[1];
|
|
|
|
add(hv[12], hv[13], gh, gl, tc);
|
|
hv[12] = tc[0]; hv[13] = tc[1];
|
|
|
|
add(hv[14], hv[15], hh, hl, tc);
|
|
hv[14] = tc[0]; hv[15] = tc[1];
|
|
|
|
return hv;
|
|
}
|
|
|
|
var h384, h512, k512,
|
|
der384, der512, der512_224, der512_256,
|
|
upd = utils.unpackData;
|
|
|
|
h384 = upd("y7udXcEFnthimikqNnzVB5FZAVowcN0XFS/s2PcOWTlnMyZn/8ALMY60SodoWBUR2wwuDWT5j6dHtUgdvvpPpA==", 4, 1);
|
|
|
|
h512 = upd("agnmZ/O8yQi7Z66FhMqnOzxu83L+lPgrpU/1Ol8dNvFRDlJ/reaC0ZsFaIwrPmwfH4PZq/tBvWtb4M0ZE34heQ", 4, 1);
|
|
|
|
k512 = upd(
|
|
"QoovmNcoriJxN0SRI+9lzbXA+8/sTTsv6bXbpYGJ27w5VsJb80i1OFnxEfG2BdAZkj+CpK8ZT5urHF7" +
|
|
"V2m2BGNgHqpijAwJCEoNbAUVwb74kMYW+TuSyjFUMfcPV/7Ticr5ddPJ7iW+A3rH+OxaWsZvcBqclxx" +
|
|
"I1wZvxdM9pJpTkm2nBnvFK0u++R4Y4TyXjD8GdxouM1bUkDKHMd6ycZS3pLG9ZKwJ1SnSEqm6m5INcs" +
|
|
"KncvUH71Hb5iNqDEVO1mD5RUu5m36uoMcZtLbQyELADJ8iY+yE/v1l/x77vDuTG4AvzPaiPwtWnkUeT" +
|
|
"CqclBspjUeADgm8UKSlnCg5ucCe3CoVG0i/8LhshOFwmySZNLG38WsQq7VM4DROdlbPfZQpzVIuvY95" +
|
|
"2agq7PHeyqIHCyS5H7a7mknIshRSCNTuiv+ihTPEDZKgaZku8QjABwkuLcND4l5HHbFGjBlS+MNGS6B" +
|
|
"nW71IY1pkGJFVlqRD0DjWFV3EgKhBqoHAyu9G4GaTBFrjS0MgeN2wIUUGrUydId0zfjuuZNLC8teGbS" +
|
|
"Kg5HAyzxclaY07YqkrjQYrLW5zKT3dj43NoLm/z1rK4o3SPgu5d77L8eKVjb0MXL2CEyHgUofCrcozH" +
|
|
"AggaZDnskL7/+iNjHiikUGzr3oK96b75o/eyxnkVxnF48uNyUyvKJz7O6iZhnNGGuMchwMIH6tp91s3" +
|
|
"g6x71fU9/7m7ReAbwZ6pyF2+6CmN9xaLImKYRP5gEvvkNrhtxCzUTHEcbKNt39SMEfYQyyqt7QMckkz" +
|
|
"yevgoVyb68Qx1nxJwQDUxMxdS+yz5Ctll/KZz8ZX4qX8tvqzrW+uxsRBmMSkdYFw==", 4, 1);
|
|
|
|
// DER encoding
|
|
der384 = upd("MEEwDQYJYIZIAWUDBAICBQAEMA");
|
|
der512 = upd("MFEwDQYJYIZIAWUDBAIDBQAEQA");
|
|
der512_224 = upd("MC0wDQYJYIZIAWUDBAIFBQAEHA");
|
|
der512_256 = upd("MDEwDQYJYIZIAWUDBAIGBQAEIA");
|
|
|
|
return {
|
|
sha384: msrcryptoSha("SHA-384", der384, h384, k512, 128, hashBlock, 384),
|
|
sha512: msrcryptoSha("SHA-512", der512, h512, k512, 128, hashBlock, 512),
|
|
sha512_224: msrcryptoSha("SHA-512.224", der512_224, h512, k512, 128, hashBlock, 224),
|
|
sha512_256: msrcryptoSha("SHA-512.256", der512_256, h512, k512, 128, hashBlock, 256)
|
|
};
|
|
|
|
})();
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoSha512.hash384 = function ( p) {
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoSha512.sha384.process(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoSha512.sha384.finish();
|
|
}
|
|
|
|
return msrcryptoSha512.sha384.computeHash(p.buffer);
|
|
|
|
};
|
|
|
|
msrcryptoSha512.hash512 = function ( p) {
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoSha512.sha512.process(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoSha512.sha512.finish();
|
|
}
|
|
|
|
return msrcryptoSha512.sha512.computeHash(p.buffer);
|
|
|
|
};
|
|
|
|
operations.register("digest", "sha-384", msrcryptoSha512.hash384);
|
|
operations.register("digest", "sha-512", msrcryptoSha512.hash512);
|
|
}
|
|
|
|
msrcryptoHashFunctions["sha-384"] = msrcryptoSha512.sha384;
|
|
msrcryptoHashFunctions["sha-512"] = msrcryptoSha512.sha512;
|
|
|
|
var msrcryptoHmac = (function () {
|
|
|
|
var sha256,
|
|
sha512,
|
|
sha1;
|
|
|
|
if (typeof msrcryptoSha256 !== "undefined") {
|
|
sha256 = msrcryptoSha256;
|
|
}
|
|
|
|
if (typeof msrcryptoSha512 !== "undefined") {
|
|
sha512 = msrcryptoSha512;
|
|
}
|
|
|
|
if (typeof msrcryptoSha1 !== "undefined") {
|
|
sha1 = msrcryptoSha1;
|
|
}
|
|
|
|
var hashFunction;
|
|
var blockSize;
|
|
var keyBytes;
|
|
|
|
var ipad;
|
|
var opad;
|
|
|
|
function xorArrays(array1, array2) {
|
|
var newArray = new Array(array1);
|
|
for (var i = 0 ; i < array1.length; i++) {
|
|
newArray[i] = array1[i] ^ array2[i];
|
|
}
|
|
return newArray;
|
|
}
|
|
|
|
// Returns a new Array with zeroes padded on the end
|
|
function padZeros(bytes, paddedLength) {
|
|
var paddedArray = bytes.slice();
|
|
for (var i = bytes.length ; i < paddedLength; i++) {
|
|
paddedArray.push(0);
|
|
}
|
|
return paddedArray;
|
|
}
|
|
|
|
function padKey() {
|
|
|
|
if (keyBytes.length === blockSize) {
|
|
return keyBytes;
|
|
}
|
|
|
|
if (keyBytes.length > blockSize) {
|
|
return padZeros(hashFunction.computeHash(keyBytes), blockSize);
|
|
}
|
|
|
|
// If keyBytes.length < blockSize
|
|
return padZeros(keyBytes, blockSize);
|
|
|
|
}
|
|
|
|
var paddedKey = null;
|
|
var keyXorOpad;
|
|
|
|
function processHmac(messageBytes) {
|
|
|
|
var keyXorIpad;
|
|
var k0IpadText;
|
|
|
|
// If this is the first process call, do some initial computations
|
|
if (!paddedKey) {
|
|
ipad = new Array(blockSize);
|
|
opad = new Array(blockSize);
|
|
for (var i = 0; i < blockSize; i++) { ipad[i] = 0x36; opad[i] = 0x5c; }
|
|
|
|
paddedKey = padKey();
|
|
keyXorIpad = xorArrays(paddedKey, ipad);
|
|
keyXorOpad = xorArrays(paddedKey, opad);
|
|
k0IpadText = keyXorIpad.concat(messageBytes);
|
|
hashFunction.process(k0IpadText);
|
|
|
|
// Subsequent process calls just add to the hash
|
|
} else {
|
|
|
|
hashFunction.process(messageBytes);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
function finishHmac() {
|
|
|
|
var hashK0IpadText = hashFunction.finish();
|
|
|
|
var k0IpadK0OpadText = keyXorOpad.concat(hashK0IpadText);
|
|
|
|
return hashFunction.computeHash(k0IpadK0OpadText);
|
|
}
|
|
|
|
function clearState() {
|
|
keyBytes = null;
|
|
hashFunction = null;
|
|
paddedKey = null;
|
|
}
|
|
|
|
function selectHashAlgorithm(hashAlgorithmName) {
|
|
|
|
switch (hashAlgorithmName.toLowerCase()) {
|
|
|
|
case "sha-1":
|
|
if (sha1 === undefined) {
|
|
throw new Error("Sha1 object not found");
|
|
}
|
|
hashFunction = sha1.sha1;
|
|
blockSize = 64;
|
|
break;
|
|
|
|
case "sha-224":
|
|
hashFunction = sha256.sha224;
|
|
blockSize = 64;
|
|
break;
|
|
|
|
case "sha-256":
|
|
hashFunction = sha256.sha256;
|
|
blockSize = 64;
|
|
break;
|
|
|
|
case "sha-384":
|
|
if (sha512 === undefined) {
|
|
throw new Error("Sha512 object not found");
|
|
}
|
|
hashFunction = sha512.sha384;
|
|
blockSize = 128;
|
|
break;
|
|
|
|
case "sha-512":
|
|
if (sha512 === undefined) {
|
|
throw new Error("Sha512 object not found");
|
|
}
|
|
hashFunction = sha512.sha512;
|
|
blockSize = 128;
|
|
break;
|
|
|
|
default:
|
|
throw new Error("unsupported hash alorithm (sha-224, sha-256, sha-384, sha-512)");
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
computeHmac: function (dataBytes, key, hashAlgorithm) {
|
|
/// <summary>Computes the HMAC</summary>
|
|
/// <param name="dataBytes" type="Array">Data to MAC</param>
|
|
/// <param name="key" type="Array">Array of bytes for key</param>
|
|
/// <param name="hashAlgorithm" type="String">sha-224, sha-256, sha-384, sha-512 (default sha-256)</param>
|
|
/// <returns type="Array">Returns an array of bytes as the HMAC</returns>
|
|
|
|
keyBytes = key;
|
|
|
|
selectHashAlgorithm(hashAlgorithm);
|
|
|
|
processHmac(dataBytes);
|
|
|
|
var result = finishHmac();
|
|
|
|
clearState();
|
|
|
|
return result;
|
|
},
|
|
|
|
process: function (dataBytes, key, hashAlgorithm) {
|
|
/// <summary>Computes a partial HMAC to be followed by subsequent process calls or finish()</summary>
|
|
/// <param name="dataBytes" type="Array">Data to MAC</param>
|
|
/// <param name="key" type="Array">Array of bytes for key</param>
|
|
/// <param name="hashAlgorithm" type="String">sha-224, sha-256, sha-384, sha-512 (default sha-256)</param>
|
|
|
|
if (!hashFunction) {
|
|
keyBytes = key;
|
|
selectHashAlgorithm(hashAlgorithm);
|
|
}
|
|
|
|
processHmac(dataBytes);
|
|
},
|
|
|
|
finish: function (key, hashAlgorithm) {
|
|
/// <summary>Computes the final HMAC upon partial computations from previous process() calls.</summary>
|
|
/// <param name="key" type="Array">Array of bytes for key</param>
|
|
/// <param name="hashAlgorithm" type="String">sha-224, sha-256, sha-384, sha-512 (default sha-256)</param>
|
|
/// <returns type="Array">Returns an array of bytes as the HMAC</returns>
|
|
|
|
// Finish could be called before any processing. We'll return the hmac
|
|
// of an empty buffer.
|
|
if (!hashFunction) {
|
|
keyBytes = key;
|
|
selectHashAlgorithm(hashAlgorithm);
|
|
processHmac([]);
|
|
}
|
|
|
|
var result = finishHmac();
|
|
clearState();
|
|
return result;
|
|
}
|
|
|
|
};
|
|
})();
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoHmac.signHmac = function (p) {
|
|
|
|
var hashName = p.algorithm.hash.name;
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoHmac.process(p.buffer, p.keyData, hashName);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoHmac.finish(p.keyData, hashName);
|
|
}
|
|
|
|
return msrcryptoHmac.computeHmac(p.buffer, p.keyData, hashName);
|
|
};
|
|
|
|
msrcryptoHmac.verifyHmac = function (p) {
|
|
|
|
var hashName = p.algorithm.hash.name;
|
|
|
|
if (p.operationSubType === "process") {
|
|
msrcryptoHmac.process(p.buffer, p.keyData, hashName);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
return msrcryptoUtilities.arraysEqual(msrcryptoHmac.finish(p.keyData, hashName), p.signature);
|
|
}
|
|
|
|
return msrcryptoUtilities.arraysEqual(msrcryptoHmac.computeHmac(p.buffer, p.keyData, hashName), p.signature);
|
|
};
|
|
|
|
msrcryptoHmac.generateKey = function (p) {
|
|
|
|
var keyLength = p.algorithm.length;
|
|
|
|
var defaultKeyLengths = { "sha-256": 32, "sha-384": 48, "sha-512": 64 };
|
|
|
|
if (!keyLength) {
|
|
keyLength = defaultKeyLengths[p.algorithm.hash.name.toLowerCase()];
|
|
}
|
|
|
|
return {
|
|
type: "keyGeneration",
|
|
keyData: msrcryptoPseudoRandom.getBytes(keyLength),
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "secret"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoHmac.importKey = function (p) {
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["k"]);
|
|
|
|
keyObject.alg = keyObject.alg.replace("HS", "sha-");
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject.k,
|
|
keyHandle: {
|
|
algorithm: { name: "hmac", hash: { name: keyObject.alg } },
|
|
extractable: p.extractable || keyObject.extractable,
|
|
keyUsage: null || p.keyUsage, // IE11 returns null here
|
|
type: "secret"
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
msrcryptoHmac.exportKey = function (p) {
|
|
|
|
var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);
|
|
|
|
return { type: "keyExport", keyHandle: jsonKeyStringArray };
|
|
};
|
|
|
|
operations.register("importKey", "hmac", msrcryptoHmac.importKey);
|
|
operations.register("exportKey", "hmac", msrcryptoHmac.exportKey);
|
|
operations.register("generateKey", "hmac", msrcryptoHmac.generateKey);
|
|
operations.register("sign", "hmac", msrcryptoHmac.signHmac);
|
|
operations.register("verify", "hmac", msrcryptoHmac.verifyHmac);
|
|
}
|
|
|
|
var msrcryptoBlockCipher = (function() {
|
|
|
|
var aesConstants,
|
|
multByTwo,
|
|
multByThree,
|
|
multBy14,
|
|
multBy13,
|
|
multBy11,
|
|
multBy9,
|
|
sBoxTable,
|
|
invSBoxTable,
|
|
rConTable;
|
|
|
|
return {
|
|
|
|
/// <summary>Advanced Encryption Standard implementation per FIPS 197.</summary>
|
|
aes: function ( keyBytes) {
|
|
|
|
// Set up the constants the first time we create an AES object only.
|
|
if (!aesConstants) {
|
|
aesConstants = msrcryptoUtilities.unpackData("AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD5AQkRGSEpMTlBSVFZYWlxeYGJkZmhqbG5wcnR2eHp8foCChIaIioyOkJKUlpianJ6goqSmqKqsrrCytLa4ury+wMLExsjKzM7Q0tTW2Nrc3uDi5Obo6uzu8PL09vj6/P4bGR8dExEXFQsJDw0DAQcFOzk/PTMxNzUrKS8tIyEnJVtZX11TUVdVS0lPTUNBR0V7eX99c3F3dWtpb21jYWdlm5mfnZORl5WLiY+Ng4GHhbu5v72zsbe1q6mvraOhp6Xb2d/d09HX1cvJz83DwcfF+/n//fPx9/Xr6e/t4+Hn5QADBgUMDwoJGBseHRQXEhEwMzY1PD86OSgrLi0kJyIhYGNmZWxvaml4e359dHdycVBTVlVcX1pZSEtOTURHQkHAw8bFzM/Kydjb3t3U19LR8PP29fz/+vno6+7t5Ofi4aCjpqWsr6qpuLu+vbS3srGQk5aVnJ+amYiLjo2Eh4KBm5idnpeUkZKDgIWGj4yJiquora6npKGis7C1tr+8ubr7+P3+9/Tx8uPg5ebv7Onqy8jNzsfEwcLT0NXW39zZ2ltYXV5XVFFSQ0BFRk9MSUpraG1uZ2RhYnNwdXZ/fHl6Ozg9Pjc0MTIjICUmLywpKgsIDQ4HBAECExAVFh8cGRoADhwSODYkKnB+bGJIRlRa4O788tjWxMqQnoyCqKa0utvVx8nj7f/xq6W3uZOdj4E7NScpAw0fEUtFV1lzfW9hraOxv5WbiYfd08HP5ev5901DUV91e2lnPTMhLwULGRd2eGpkTkBSXAYIGhQ+MCIslpiKhK6gsrzm6Pr03tDCzEFPXVN5d2VrMT8tIwkHFRuhr72zmZeFi9HfzcPp5/X7mpSGiKKsvrDq5Pb40tzOwHp0ZmhCTF5QCgQWGDI8LiDs4vD+1NrIxpySgI6kqri2DAIQHjQ6KCZ8cmBuREpYVjc5KyUPARMdR0lbVX9xY23X2cvF7+Hz/aepu7WfkYONAA0aFzQ5LiNoZXJ/XFFGS9Ddysfk6f7zuLWir4yBlpu7tqGsj4KVmNPeycTn6v3wa2ZxfF9SRUgDDhkUNzotIG1gd3pZVENOBQgfEjE8Kya9sKeqiYSTntXYz8Lh7Pv21tvMweLv+PW+s6SpioeQnQYLHBEyPyglbmN0eVpXQE3a18DN7uP0+bK/qKWGi5yRCgcQHT4zJClib3h1VltMQWFse3ZVWE9CCQQTHj0wJyqxvKumhYifktnUw87t4Pf6t7qtoIOOmZTf0sXI6+bx/GdqfXBTXklEDwIVGDs2ISwMARYbODUiL2RpfnNQXUpH3NHGy+jl8v+0ua6jgI2alwALFh0sJzoxWFNORXR/Ymmwu6atnJeKgejj/vXEz9LZe3BtZldcQUojKDU+DwQZEsvA3dbn7PH6k5iFjr+0qaL2/eDr2tHMx66luLOCiZSfRk1QW2phfHceFQgDMjkkL42Gm5Chqre81d7DyPny7+Q9NisgERoHDGVuc3hJQl9U9/zh6tvQzcavpLmyg4iVnkdMUVprYH12HxQJAjM4JS6Mh5qRoKu2vdTfwsn48+7lPDcqIRAbBg1kb3J5SENeVQEKFxwtJjswWVJPRHV+Y2ixuqesnZaLgOni//TFztPYenFsZ1ZdQEsiKTQ/DgUYE8rB3Nfm7fD7kpmEj761qKMACRIbJC02P0hBWlNsZX53kJmCi7S9pq/Y0crD/PXu5zsyKSAfFg0Ec3phaFdeRUyrormwj4adlOPq8fjHztXcdn9kbVJbQEk+NywlGhMIAebv9P3Cy9DZrqe8tYqDmJFNRF9WaWB7cgUMFx4hKDM63dTPxvnw6+KVnIeOsbijquzl/vfIwdrTpK22v4CJkpt8dW5nWFFKQzQ9Ji8QGQIL197FzPP64eiflo2Eu7KpoEdOVVxjanF4DwYdFCsiOTCak4iBvrespdLbwMn2/+TtCgMYES4nPDVCS1BZZm90faGos7qFjJee6eD78s3E39YxOCMqFRwHDnlwa2JdVE9GY3x3e/Jrb8UwAWcr/terdsqCyX36WUfwrdSir5ykcsC3/ZMmNj/3zDSl5fFx2DEVBMcjwxiWBZoHEoDi6yeydQmDLBobblqgUjvWsynjL4RT0QDtIPyxW2rLvjlKTFjP0O+q+0NNM4VF+QJ/UDyfqFGjQI+SnTj1vLbaIRD/89LNDBPsX5dEF8Snfj1kXRlzYIFP3CIqkIhG7rgU3l4L2+AyOgpJBiRcwtOsYpGV5HnnyDdtjdVOqWxW9Opleq4IunglLhymtMbo3XQfS72LinA+tWZIA/YOYTVXuYbBHZ7h+JgRadmOlJseh+nOVSjfjKGJDb/mQmhBmS0PsFS7FlIJatUwNqU4v0CjnoHz1/t84zmCmy//hzSOQ0TE3unLVHuUMqbCIz3uTJULQvrDTgguoWYo2SSydluiSW2L0SVy+PZkhmiYFtSkXMxdZbaSbHBIUP3tudpeFUZXp42dhJDYqwCMvNMK9+RYBbizRQbQLB6Pyj8PAsGvvQMBE4prOpERQU9n3OqX8s/O8LTmc5asdCLnrTWF4vk36Bx1325H8RpxHSnFiW+3Yg6qGL4b/FY+S8bSeSCa28D+eM1a9B/dqDOIB8cxsRIQWSeA7F9gUX+pGbVKDS3lep+TyZzvoOA7Ta4q9bDI67s8g1OZYRcrBH66d9Ym4WkUY1UhDH2NAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuN", 256, false);
|
|
multByTwo = aesConstants[0];
|
|
multByThree = aesConstants[1];
|
|
multBy14 = aesConstants[2];
|
|
multBy13 = aesConstants[3];
|
|
multBy11 = aesConstants[4];
|
|
multBy9 = aesConstants[5];
|
|
sBoxTable = aesConstants[6];
|
|
invSBoxTable = aesConstants[7];
|
|
rConTable = aesConstants[8];
|
|
}
|
|
|
|
var blockSize = 128,
|
|
keyLength,
|
|
nK,
|
|
nB = 4,
|
|
nR,
|
|
key;
|
|
|
|
switch (keyBytes.length * 8) {
|
|
|
|
case 128:
|
|
keyLength = 128;
|
|
nK = 4;
|
|
nR = 10;
|
|
break;
|
|
|
|
case 192:
|
|
keyLength = 192;
|
|
nK = 6;
|
|
nR = 12;
|
|
break;
|
|
|
|
case 256:
|
|
keyLength = 256;
|
|
nK = 8;
|
|
nR = 14;
|
|
break;
|
|
|
|
default:
|
|
throw new Error("Unsupported keyLength");
|
|
}
|
|
|
|
var shiftRows = function (a) {
|
|
var tmp = a[1]; a[1] = a[5]; a[5] = a[9]; a[9] = a[13]; a[13] = tmp;
|
|
tmp = a[2]; a[2] = a[10]; a[10] = tmp;
|
|
tmp = a[6]; a[6] = a[14]; a[14] = tmp;
|
|
tmp = a[15]; a[15] = a[11]; a[11] = a[7]; a[7] = a[3]; a[3] = tmp;
|
|
};
|
|
|
|
var invShiftRows = function (a) {
|
|
var tmp = a[13]; a[13] = a[9]; a[9] = a[5]; a[5] = a[1]; a[1] = tmp;
|
|
tmp = a[10]; a[10] = a[2]; a[2] = tmp;
|
|
tmp = a[14]; a[14] = a[6]; a[6] = tmp;
|
|
tmp = a[3]; a[3] = a[7]; a[7] = a[11]; a[11] = a[15]; a[15] = tmp;
|
|
};
|
|
|
|
var mixColumns = function (state) {
|
|
/// <summary>Operates on the state column by column, performing a multiplication by x^4 + 1 in GF(2^8)</summary>
|
|
/// <param name="state" type="Array"> the current state (length 16)</param>
|
|
/// <returns type="Array">The mixed state</returns>
|
|
var a = state[0], b = state[1], c = state[2], d = state[3],
|
|
e = state[4], f = state[5], g = state[6], h = state[7],
|
|
i = state[8], j = state[9], k = state[10], l = state[11],
|
|
m = state[12], n = state[13], o = state[14], p = state[15];
|
|
|
|
state[0] = multByTwo[a] ^ multByThree[b] ^ c ^ d;
|
|
state[1] = a ^ multByTwo[b] ^ multByThree[c] ^ d;
|
|
state[2] = a ^ b ^ multByTwo[c] ^ multByThree[d];
|
|
state[3] = multByThree[a] ^ b ^ c ^ multByTwo[d];
|
|
state[4] = multByTwo[e] ^ multByThree[f] ^ g ^ h;
|
|
state[5] = e ^ multByTwo[f] ^ multByThree[g] ^ h;
|
|
state[6] = e ^ f ^ multByTwo[g] ^ multByThree[h];
|
|
state[7] = multByThree[e] ^ f ^ g ^ multByTwo[h];
|
|
state[8] = multByTwo[i] ^ multByThree[j] ^ k ^ l;
|
|
state[9] = i ^ multByTwo[j] ^ multByThree[k] ^ l;
|
|
state[10] = i ^ j ^ multByTwo[k] ^ multByThree[l];
|
|
state[11] = multByThree[i] ^ j ^ k ^ multByTwo[l];
|
|
state[12] = multByTwo[m] ^ multByThree[n] ^ o ^ p;
|
|
state[13] = m ^ multByTwo[n] ^ multByThree[o] ^ p;
|
|
state[14] = m ^ n ^ multByTwo[o] ^ multByThree[p];
|
|
state[15] = multByThree[m] ^ n ^ o ^ multByTwo[p];
|
|
};
|
|
|
|
var invMixColumns = function (state) {
|
|
/// <summary>Operates on the state column by column, performing a multiplication by x^4 + 1 in GF(2^8)</summary>
|
|
/// <param name="state" type="Array"> the current state (length 16)</param>
|
|
/// <returns type="Array">The mixed state</returns>
|
|
var a = state[0], b = state[1], c = state[2], d = state[3],
|
|
e = state[4], f = state[5], g = state[6], h = state[7],
|
|
i = state[8], j = state[9], k = state[10], l = state[11],
|
|
m = state[12], n = state[13], o = state[14], p = state[15];
|
|
|
|
state[0] = multBy14[a] ^ multBy11[b] ^ multBy13[c] ^ multBy9[d];
|
|
state[1] = multBy9[a] ^ multBy14[b] ^ multBy11[c] ^ multBy13[d];
|
|
state[2] = multBy13[a] ^ multBy9[b] ^ multBy14[c] ^ multBy11[d];
|
|
state[3] = multBy11[a] ^ multBy13[b] ^ multBy9[c] ^ multBy14[d];
|
|
state[4] = multBy14[e] ^ multBy11[f] ^ multBy13[g] ^ multBy9[h];
|
|
state[5] = multBy9[e] ^ multBy14[f] ^ multBy11[g] ^ multBy13[h];
|
|
state[6] = multBy13[e] ^ multBy9[f] ^ multBy14[g] ^ multBy11[h];
|
|
state[7] = multBy11[e] ^ multBy13[f] ^ multBy9[g] ^ multBy14[h];
|
|
state[8] = multBy14[i] ^ multBy11[j] ^ multBy13[k] ^ multBy9[l];
|
|
state[9] = multBy9[i] ^ multBy14[j] ^ multBy11[k] ^ multBy13[l];
|
|
state[10] = multBy13[i] ^ multBy9[j] ^ multBy14[k] ^ multBy11[l];
|
|
state[11] = multBy11[i] ^ multBy13[j] ^ multBy9[k] ^ multBy14[l];
|
|
state[12] = multBy14[m] ^ multBy11[n] ^ multBy13[o] ^ multBy9[p];
|
|
state[13] = multBy9[m] ^ multBy14[n] ^ multBy11[o] ^ multBy13[p];
|
|
state[14] = multBy13[m] ^ multBy9[n] ^ multBy14[o] ^ multBy11[p];
|
|
state[15] = multBy11[m] ^ multBy13[n] ^ multBy9[o] ^ multBy14[p];
|
|
};
|
|
|
|
var xorWord = function (a, b) {
|
|
return [a[0] ^ b[0], a[1] ^ b[1], a[2] ^ b[2], a[3] ^ b[3]];
|
|
};
|
|
|
|
var addRoundKey = function ( state, keySchedule, offset) {
|
|
for (var i = 0 ; i < state.length ; i += 1) {
|
|
state[i] ^= keySchedule[i + offset];
|
|
}
|
|
};
|
|
|
|
var rotWord = function ( word) {
|
|
var a = word[0];
|
|
word[0] = word[1]; word[1] = word[2]; word[2] = word[3]; word[3] = a;
|
|
};
|
|
|
|
var subWord = function ( word) {
|
|
for (var i = 0 ; i < word.length ; i += 1) {
|
|
word[i] = sBoxTable[word[i]];
|
|
}
|
|
};
|
|
|
|
var invSubWord = function ( word) {
|
|
for (var i = 0 ; i < word.length ; i += 1) {
|
|
word[i] = invSBoxTable[word[i]];
|
|
}
|
|
};
|
|
|
|
var getWord = function (tab, i) {
|
|
return [tab[4 * i], tab[4 * i + 1], tab[4 * i + 2], tab[4 * i + 3]];
|
|
};
|
|
|
|
var setWord = function ( left, right, indexL, indexR) {
|
|
left[4 * indexL] = right[4 * indexR];
|
|
left[4 * indexL + 1] = right[4 * indexR + 1];
|
|
left[4 * indexL + 2] = right[4 * indexR + 2];
|
|
left[4 * indexL + 3] = right[4 * indexR + 3];
|
|
};
|
|
|
|
var expandKey = function (key) {
|
|
var temp, res = [], i = 0;
|
|
while (i < 4 * nK) {
|
|
res.push(key[i++]);
|
|
}
|
|
|
|
i = nK;
|
|
while (i < nB * (nR + 1)) {
|
|
temp = getWord(res, i - 1);
|
|
if (i % nK === 0) {
|
|
var index = i / nK;
|
|
var rcon = [rConTable[index], 0, 0, 0];
|
|
rotWord(temp);
|
|
subWord(temp);
|
|
temp = xorWord(temp, rcon);
|
|
} else if (nK > 6 && i % nK === 4) {
|
|
subWord(temp);
|
|
}
|
|
var newWord = xorWord(getWord(res, i - nK), temp);
|
|
setWord(res, newWord, i, 0);
|
|
i += 1;
|
|
}
|
|
return res;
|
|
};
|
|
|
|
key = expandKey(keyBytes);
|
|
|
|
return {
|
|
|
|
encrypt: function (dataBytes) {
|
|
var state = dataBytes,
|
|
round;
|
|
|
|
addRoundKey(state, key, 0);
|
|
for (round = 1 ; round <= nR - 1 ; round += 1) {
|
|
subWord(state);
|
|
shiftRows(state);
|
|
mixColumns(state);
|
|
addRoundKey(state, key, 4 * round * nB);
|
|
}
|
|
subWord(state);
|
|
shiftRows(state);
|
|
addRoundKey(state, key, 4 * nR * nB);
|
|
|
|
return state;
|
|
},
|
|
|
|
decrypt: function (dataBytes) {
|
|
var state = dataBytes,
|
|
round;
|
|
|
|
addRoundKey(state, key, 4 * nR * nB);
|
|
for (round = nR - 1 ; round >= 1 ; round -= 1) {
|
|
invShiftRows(state);
|
|
invSubWord(state);
|
|
addRoundKey(state, key, 4 * round * nB);
|
|
invMixColumns(state);
|
|
}
|
|
invShiftRows(state);
|
|
invSubWord(state);
|
|
addRoundKey(state, key, 0);
|
|
|
|
return state;
|
|
},
|
|
|
|
clear: function () {
|
|
// Reset the state
|
|
},
|
|
|
|
keyLength: keyLength,
|
|
|
|
blockSize: blockSize
|
|
|
|
};
|
|
}
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
var msrcryptoPadding = msrcryptoPadding || {};
|
|
|
|
msrcryptoPadding.pkcsv7 = function (blockSize) {
|
|
|
|
function pad(messageBlocks) {
|
|
/// <signature>
|
|
/// <summary>Apply PKCS7 padding to message</summary>
|
|
/// <param name="messageBlocks" type="Array">An array of blocks to pad</param> <
|
|
/// </signature>
|
|
|
|
var lastIndex = messageBlocks.length - 1 >= 0 ? messageBlocks.length - 1 : 0,
|
|
lastBlock = messageBlocks[lastIndex],
|
|
lastBlockLength = lastBlock.length,
|
|
createNewBlock = (lastBlockLength === blockSize);
|
|
|
|
if (createNewBlock) {
|
|
var newBlock = [], i;
|
|
for (i = 0 ; i < blockSize; i += 1) {
|
|
newBlock.push(blockSize);
|
|
}
|
|
messageBlocks.push(newBlock);
|
|
} else {
|
|
var byteToAdd = (blockSize - lastBlockLength) & 0xff;
|
|
while (lastBlock.length !== blockSize) {
|
|
lastBlock.push(byteToAdd);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
function unpad(messageBytes) {
|
|
/// <signature>
|
|
/// <summary>Remove PKCS7 padding from the message</summary>
|
|
/// <param name="messageBytes" type="Array"></param>
|
|
/// <returns type="Boolean">True for legal padding. False if not.</returns>
|
|
/// </signature>
|
|
|
|
var verified = true;
|
|
|
|
// Verify the cipher text is an increment of block length
|
|
if (messageBytes.length % blockSize !== 0) {
|
|
verified = false;
|
|
}
|
|
|
|
// Get the last block
|
|
var lastBlock = messageBytes.slice(-blockSize);
|
|
|
|
// Get value of the last element in the block
|
|
// This will be the number of padding bytes on the end if the
|
|
// message was decrypted correctly.
|
|
var padLen = lastBlock[lastBlock.length - 1];
|
|
|
|
for (var i = 0; i < blockSize; i++) {
|
|
var isPaddingElement = (blockSize - i <= padLen);
|
|
var isCorrectValue = (lastBlock[i] === padLen);
|
|
verified = (isPaddingElement ? isCorrectValue : true) && verified;
|
|
}
|
|
|
|
var trimLen = verified ? padLen : 0;
|
|
|
|
messageBytes.length -= trimLen;
|
|
|
|
return verified;
|
|
}
|
|
|
|
return {
|
|
pad: pad,
|
|
unpad: unpad
|
|
};
|
|
|
|
};
|
|
|
|
var msrcryptoCbc = function (blockCipher) {
|
|
|
|
var blockSize = blockCipher.blockSize / 8;
|
|
|
|
var paddingScheme = msrcryptoPadding.pkcsv7(blockSize);
|
|
|
|
// Merges an array of block arrays into a single byte array
|
|
var mergeBlocks = function ( tab) {
|
|
var res = [], i, j;
|
|
for (i = 0 ; i < tab.length; i += 1) {
|
|
var block = tab[i];
|
|
for (j = 0 ; j < block.length; j += 1) {
|
|
res.push(block[j]);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
|
|
// Breaks an array of bytes into an array of block size arrays of bytes
|
|
function getBlocks(dataBytes) {
|
|
|
|
var blocks = [];
|
|
|
|
// Append incoming bytes to the end of the existing buffered bytes
|
|
mBuffer = mBuffer.concat(dataBytes);
|
|
|
|
var blockCount = Math.floor(mBuffer.length / blockSize);
|
|
|
|
for (var i = 0; i < blockCount; i++) {
|
|
blocks.push(mBuffer.slice(i * blockSize, (i + 1) * blockSize));
|
|
}
|
|
|
|
// Set the buffer to the remaining bytes
|
|
mBuffer = mBuffer.slice(blockCount * blockSize);
|
|
|
|
return blocks;
|
|
}
|
|
|
|
function encryptBlocks(blocks) {
|
|
|
|
var result = [],
|
|
toEncrypt;
|
|
|
|
for (var i = 0; i < blocks.length; i++) {
|
|
toEncrypt = msrcryptoUtilities.xorVectors(mIvBytes, blocks[i]);
|
|
result.push(blockCipher.encrypt(toEncrypt));
|
|
mIvBytes = result[i];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function decryptBlocks(blocks) {
|
|
|
|
var result = [],
|
|
toDecrypt,
|
|
decrypted;
|
|
|
|
for (var i = 0 ; i < blocks.length; i += 1) {
|
|
toDecrypt = blocks[i].slice(0, blocks[i].length);
|
|
decrypted = blockCipher.decrypt(toDecrypt);
|
|
result.push(msrcryptoUtilities.xorVectors(mIvBytes, decrypted));
|
|
mIvBytes = blocks[i];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function clearState() {
|
|
mBuffer = [];
|
|
mResultBuffer = [];
|
|
mIvBytes = null;
|
|
}
|
|
|
|
var mBuffer = [],
|
|
mResultBuffer = [],
|
|
mIvBytes;
|
|
|
|
return {
|
|
|
|
init: function (ivBytes) {
|
|
|
|
if (ivBytes.length !== blockSize) {
|
|
throw new Error("Invalid iv size");
|
|
}
|
|
|
|
mIvBytes = ivBytes.slice();
|
|
},
|
|
|
|
// Does a full encryption of the input
|
|
encrypt: function (plainBytes) {
|
|
/// <summary>perform the encryption of the plain text message</summary>
|
|
/// <param name="plainBytes" type="Array">the plain text to encrypt</param>
|
|
/// <returns type="Array">the encrypted message</returns>
|
|
|
|
this.processEncrypt(plainBytes);
|
|
|
|
return this.finishEncrypt();
|
|
},
|
|
|
|
// Encrypts full blocks of streamed input
|
|
processEncrypt: function (plainBytes) {
|
|
|
|
var result = encryptBlocks(getBlocks(plainBytes));
|
|
|
|
mResultBuffer = mResultBuffer.concat(mergeBlocks(result));
|
|
|
|
return;
|
|
},
|
|
|
|
// Call when done streaming input
|
|
finishEncrypt: function () {
|
|
|
|
var blocks = mBuffer.length === 1 ? [[mBuffer[0]]] : [mBuffer];
|
|
|
|
paddingScheme.pad(blocks);
|
|
|
|
var result = mResultBuffer.concat(mergeBlocks(encryptBlocks(blocks)));
|
|
|
|
clearState();
|
|
|
|
return result;
|
|
},
|
|
|
|
// Does a full decryption and returns the result
|
|
decrypt: function ( cipherBytes) {
|
|
/// <summary>perform the decryption of the encrypted message</summary>
|
|
/// <param name="encryptedBytes" type="Array">the plain text to encrypt</param>
|
|
/// <returns type="Array">the encrypted message</returns>
|
|
|
|
this.processDecrypt(cipherBytes);
|
|
|
|
return this.finishDecrypt();
|
|
},
|
|
|
|
// Decrypts full blocks of streamed data
|
|
processDecrypt: function (cipherBytes) {
|
|
|
|
var result = decryptBlocks(getBlocks(cipherBytes));
|
|
|
|
mResultBuffer = mResultBuffer.concat(mergeBlocks(result));
|
|
|
|
return;
|
|
},
|
|
|
|
// Called to finalize streamed decryption
|
|
finishDecrypt: function () {
|
|
|
|
var result = mResultBuffer;
|
|
|
|
// Strip the padding.
|
|
var verified = paddingScheme.unpad(result);
|
|
|
|
clearState();
|
|
|
|
return result;
|
|
}
|
|
|
|
};
|
|
};
|
|
|
|
var cbcInstance = null;
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoCbc.workerEncrypt = function (p) {
|
|
|
|
var result;
|
|
|
|
if (!cbcInstance) {
|
|
cbcInstance = msrcryptoCbc(msrcryptoBlockCipher.aes(p.keyData));
|
|
cbcInstance.init(p.algorithm.iv);
|
|
}
|
|
|
|
if (p.operationSubType === "process") {
|
|
cbcInstance.processEncrypt(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
result = cbcInstance.finishEncrypt();
|
|
cbcInstance = null;
|
|
return result;
|
|
}
|
|
|
|
result = cbcInstance.encrypt(p.buffer);
|
|
cbcInstance = null;
|
|
return result;
|
|
};
|
|
|
|
msrcryptoCbc.workerDecrypt = function (p) {
|
|
|
|
var result;
|
|
|
|
if (!cbcInstance) {
|
|
cbcInstance = msrcryptoCbc(msrcryptoBlockCipher.aes(p.keyData));
|
|
cbcInstance.init(p.algorithm.iv);
|
|
}
|
|
|
|
if (p.operationSubType === "process") {
|
|
cbcInstance.processDecrypt(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
result = cbcInstance.finishDecrypt();
|
|
cbcInstance = null;
|
|
return result;
|
|
}
|
|
|
|
result = cbcInstance.decrypt(p.buffer);
|
|
cbcInstance = null;
|
|
return result;
|
|
};
|
|
|
|
msrcryptoCbc.generateKey = function (p) {
|
|
|
|
if (p.algorithm.length % 8 !== 0) {
|
|
throw new Error();
|
|
}
|
|
|
|
return {
|
|
type: "keyGeneration",
|
|
keyData: msrcryptoPseudoRandom.getBytes(Math.floor(p.algorithm.length / 8)),
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "secret"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoCbc.importKey = function (p) {
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["k"]);
|
|
|
|
p.algorithm.length = keyObject.k.length * 8;
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject.k,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable || keyObject.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "secret"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoCbc.exportKey = function (p) {
|
|
|
|
var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);
|
|
|
|
return { type: "keyExport", keyHandle: jsonKeyStringArray };
|
|
};
|
|
|
|
operations.register("importKey", "aes-cbc", msrcryptoCbc.importKey);
|
|
operations.register("exportKey", "aes-cbc", msrcryptoCbc.exportKey);
|
|
operations.register("generateKey", "aes-cbc", msrcryptoCbc.generateKey);
|
|
operations.register("encrypt", "aes-cbc", msrcryptoCbc.workerEncrypt);
|
|
operations.register("decrypt", "aes-cbc", msrcryptoCbc.workerDecrypt);
|
|
}
|
|
|
|
var msrcryptoGcm = function ( blockCipher) {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
var mBuffer = [], mIvBytes, mAdditionalBytes, mTagLength, mJ0, mJ0inc, mH = blockCipher.encrypt(utils.getVector(16)), mGHashState = utils.getVector(16), mGHashBuffer = [], mCipherText = [], mGctrCb, mBytesProcessed = 0;
|
|
|
|
function ghash( hashSubkey, dataBytes) {
|
|
|
|
var blockCount = Math.floor(dataBytes.length / 16),
|
|
dataBlock;
|
|
|
|
for (var i = 0; i < blockCount; i++) {
|
|
dataBlock = dataBytes.slice(i * 16, i * 16 + 16);
|
|
mGHashState = blockMultiplication(utils.xorVectors(mGHashState, dataBlock), hashSubkey);
|
|
}
|
|
|
|
mGHashBuffer = dataBytes.slice(blockCount * 16);
|
|
|
|
return mGHashState;
|
|
}
|
|
|
|
function finishGHash() {
|
|
|
|
var u = 16 * Math.ceil(mBytesProcessed / 16) - mBytesProcessed;
|
|
|
|
var lenA = numberTo8Bytes(mAdditionalBytes.length * 8),
|
|
lenC = numberTo8Bytes(mBytesProcessed * 8);
|
|
|
|
var p = mGHashBuffer.concat(utils.getVector(u)).concat(lenA).concat(lenC);
|
|
|
|
return ghash(mH, p);
|
|
|
|
}
|
|
|
|
function blockMultiplication( blockX, blockY) {
|
|
|
|
var z = utils.getVector(16),
|
|
v = blockY.slice(),
|
|
r = [0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
bit;
|
|
|
|
for (var i = 0; i < 128; i++) {
|
|
bit = getBit(blockX, i);
|
|
|
|
if (bit === 1) {
|
|
z = utils.xorVectors(z, v);
|
|
}
|
|
|
|
if (v[15] & 1) {
|
|
shiftRight(v);
|
|
v = utils.xorVectors(v, r);
|
|
} else {
|
|
shiftRight(v);
|
|
}
|
|
}
|
|
|
|
return z;
|
|
}
|
|
|
|
function shiftRight( dataBytes) {
|
|
|
|
for (var i = dataBytes.length - 1; i > 0; i--) {
|
|
dataBytes[i] = ((dataBytes[i - 1] & 1) << 7) | (dataBytes[i] >>> 1);
|
|
}
|
|
dataBytes[0] = dataBytes[0] >>> 1;
|
|
|
|
return dataBytes;
|
|
}
|
|
|
|
function getBit( byteArray, bitNumber) {
|
|
var byteIndex = Math.floor(bitNumber / 8);
|
|
return (byteArray[byteIndex] >> (7 - (bitNumber % 8))) & 1;
|
|
}
|
|
|
|
function inc( dataBytes) {
|
|
|
|
var carry = 256;
|
|
for (var i = 1; i <= 4; i++) {
|
|
carry = (carry >>> 8) + dataBytes[dataBytes.length - i];
|
|
dataBytes[dataBytes.length - i] = carry & 255;
|
|
}
|
|
|
|
return dataBytes;
|
|
}
|
|
|
|
function gctr( icb, dataBytes) {
|
|
|
|
var blockCount = Math.ceil(dataBytes.length / 16),
|
|
dataBlock,
|
|
result = [];
|
|
|
|
// We copy icb the first time gctr is called
|
|
if (mGctrCb !== icb) {
|
|
mGctrCb = icb.slice();
|
|
}
|
|
|
|
for (var block = 0; block < blockCount; block++) {
|
|
|
|
dataBlock = dataBytes.slice(block * 16, block * 16 + 16);
|
|
|
|
// The block cipher alters the input array, so we pass a copy.
|
|
var e = blockCipher.encrypt(mGctrCb.slice());
|
|
|
|
result = result.concat(utils.xorVectors(dataBlock, e));
|
|
|
|
mGctrCb = inc(mGctrCb);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function numberTo8Bytes(number) {
|
|
return [
|
|
0, 0, 0, 0,
|
|
(number >>> 24) & 255,
|
|
(number >>> 16) & 255,
|
|
(number >>> 8) & 255,
|
|
number & 255
|
|
];
|
|
}
|
|
|
|
function padBlocks( dataBytes) {
|
|
var padLen = 16 * Math.ceil(mAdditionalBytes.length / 16) - mAdditionalBytes.length;
|
|
return dataBytes.concat(utils.getVector(padLen));
|
|
}
|
|
|
|
function clearState() {
|
|
mBytesProcessed = 0;
|
|
mBuffer = [];
|
|
mCipherText = [];
|
|
mGHashState = utils.getVector(16);
|
|
mGHashBuffer = [];
|
|
mGctrCb = mIvBytes = mAdditionalBytes = null;
|
|
}
|
|
|
|
function init( ivBytes, additionalBytes, tagLength) {
|
|
|
|
mAdditionalBytes = additionalBytes || [];
|
|
|
|
mTagLength = isNaN(tagLength) ? 128 : tagLength;
|
|
if (mTagLength % 8 !== 0) {
|
|
throw new Error("DataError");
|
|
}
|
|
|
|
mIvBytes = ivBytes;
|
|
|
|
if (mIvBytes.length === 12) {
|
|
mJ0 = mIvBytes.concat([0, 0, 0, 1]);
|
|
|
|
} else {
|
|
var l = 16 * Math.ceil(mIvBytes.length / 16) - mIvBytes.length;
|
|
|
|
mJ0 = ghash(mH,
|
|
mIvBytes
|
|
.concat(utils.getVector(l + 8))
|
|
.concat(numberTo8Bytes(mIvBytes.length * 8)));
|
|
|
|
// Reset the ghash state so we don't affect the encrypt/decrypt ghash
|
|
mGHashState = utils.getVector(16);
|
|
}
|
|
|
|
mJ0inc = inc(mJ0.slice());
|
|
|
|
ghash(mH, padBlocks(mAdditionalBytes));
|
|
}
|
|
|
|
function encrypt( plainBytes) {
|
|
|
|
mBytesProcessed = plainBytes.length;
|
|
|
|
var c = gctr(mJ0inc, plainBytes);
|
|
|
|
ghash(mH, c);
|
|
|
|
var s = finishGHash();
|
|
|
|
var t = gctr(mJ0, s).slice(0, mTagLength / 8);
|
|
|
|
clearState();
|
|
|
|
return c.slice().concat(t);
|
|
}
|
|
|
|
function decrypt( cipherBytes, tagBytes) {
|
|
|
|
mBytesProcessed = cipherBytes.length;
|
|
|
|
var p = gctr(mJ0inc, cipherBytes);
|
|
|
|
ghash(mH, cipherBytes);
|
|
|
|
var s = finishGHash();
|
|
|
|
var t = gctr(mJ0, s).slice(0, mTagLength / 8);
|
|
|
|
clearState();
|
|
|
|
if (utils.arraysEqual(t, tagBytes)) {
|
|
return p;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function processEncrypt( plainBytes) {
|
|
|
|
// Append incoming bytes to the end of the existing buffered bytes
|
|
mBuffer = mBuffer.concat(plainBytes);
|
|
|
|
// Get a run of full blocks
|
|
var fullBlocks = mBuffer.slice(0, Math.floor(mBuffer.length / 16) * 16);
|
|
|
|
// Keep track of the total plain bytes processed
|
|
mBytesProcessed += fullBlocks.length;
|
|
|
|
// Set the buffer to the remaining bytes
|
|
mBuffer = mBuffer.slice(fullBlocks.length);
|
|
|
|
// Process the full block with gctr - gctr maintains it's own state
|
|
var c = gctr(mGctrCb || mJ0inc, fullBlocks);
|
|
|
|
mCipherText = mCipherText.concat(c);
|
|
|
|
// Process the returned blocks from gcrt
|
|
ghash(mH, c);
|
|
}
|
|
|
|
function processDecrypt( cipherBytes) {
|
|
|
|
// Append incoming bytes to the end of the existing buffered bytes
|
|
mBuffer = mBuffer.concat(cipherBytes);
|
|
|
|
// Get a run of full blocks.
|
|
// We leave enough data on the end so we don't process the tag.
|
|
var fullBlocks = mBuffer.slice(0, Math.floor((mBuffer.length - mTagLength / 8) / 16) * 16);
|
|
|
|
// Keep track of the total plain bytes processed
|
|
mBytesProcessed += fullBlocks.length;
|
|
|
|
// Set the buffer to the remaining bytes
|
|
mBuffer = mBuffer.slice(fullBlocks.length);
|
|
|
|
// Process the full block with gctr - gctr maintains it's own state
|
|
var c = gctr(mGctrCb || mJ0inc, fullBlocks);
|
|
|
|
mCipherText = mCipherText.concat(c);
|
|
|
|
// Process the returned blocks from gcrt
|
|
ghash(mH, fullBlocks);
|
|
}
|
|
|
|
function finishEncrypt() {
|
|
|
|
var c = gctr(mGctrCb, mBuffer);
|
|
|
|
mCipherText = mCipherText.concat(c);
|
|
|
|
mBytesProcessed += mBuffer.length;
|
|
|
|
var s = finishGHash();
|
|
|
|
var t = gctr(mJ0, s).slice(0, mTagLength / 8);
|
|
|
|
var result = mCipherText.slice().concat(t);
|
|
|
|
clearState();
|
|
|
|
return result;
|
|
}
|
|
|
|
function finishDecrypt() {
|
|
|
|
var tagLength = Math.floor(mTagLength / 8);
|
|
|
|
var tagBytes = mBuffer.slice( -tagLength);
|
|
|
|
mBuffer = mBuffer.slice(0, mBuffer.length - tagLength);
|
|
|
|
var c = gctr(mGctrCb, mBuffer);
|
|
|
|
mCipherText = mCipherText.concat(c);
|
|
|
|
mBytesProcessed += mBuffer.length;
|
|
|
|
var s = finishGHash();
|
|
|
|
var t = gctr(mJ0, s).slice(0, mTagLength / 8);
|
|
|
|
var result = mCipherText.slice();
|
|
|
|
clearState();
|
|
|
|
if (utils.arraysEqual(t, tagBytes)) {
|
|
return result;
|
|
} else {
|
|
throw new Error("OperationError");
|
|
}
|
|
}
|
|
|
|
return {
|
|
init: init,
|
|
encrypt: encrypt,
|
|
decrypt: decrypt,
|
|
processEncrypt: processEncrypt,
|
|
processDecrypt: processDecrypt,
|
|
finishEncrypt: finishEncrypt,
|
|
finishDecrypt: finishDecrypt
|
|
};
|
|
|
|
};
|
|
|
|
var gcm;
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoGcm.encrypt = function ( p) {
|
|
|
|
var result;
|
|
|
|
if (!gcm) {
|
|
gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(p.keyData));
|
|
gcm.init(p.algorithm.iv, p.algorithm.additionalData, p.algorithm.tagLength);
|
|
}
|
|
|
|
if (p.operationSubType === "process") {
|
|
gcm.processEncrypt(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
result = gcm.finishEncrypt();
|
|
gcm = null;
|
|
return result;
|
|
}
|
|
|
|
result = gcm.encrypt(p.buffer);
|
|
gcm = null;
|
|
return result;
|
|
};
|
|
|
|
msrcryptoGcm.decrypt = function ( p) {
|
|
|
|
var result;
|
|
|
|
if (!gcm) {
|
|
gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(p.keyData));
|
|
gcm.init(p.algorithm.iv, p.algorithm.additionalData, p.algorithm.tagLength);
|
|
}
|
|
|
|
if (p.operationSubType === "process") {
|
|
gcm.processDecrypt(p.buffer);
|
|
return;
|
|
}
|
|
|
|
if (p.operationSubType === "finish") {
|
|
result = gcm.finishDecrypt();
|
|
gcm = null;
|
|
return result;
|
|
}
|
|
|
|
var tagLength = Math.floor(p.algorithm.tagLength / 8);
|
|
var cipherBytes = p.buffer.slice(0, p.buffer.length - tagLength);
|
|
var tagBytes = p.buffer.slice( -tagLength);
|
|
|
|
result = gcm.decrypt(cipherBytes, tagBytes);
|
|
gcm = null;
|
|
return result;
|
|
};
|
|
|
|
msrcryptoGcm.generateKey = function ( p) {
|
|
|
|
if (p.algorithm.length % 8 !== 0) {
|
|
throw new Error();
|
|
}
|
|
|
|
return {
|
|
type: "keyGeneration",
|
|
keyData: msrcryptoPseudoRandom.getBytes(Math.floor(p.algorithm.length / 8)),
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "secret"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoGcm.importKey = function ( p) {
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["k"]);
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject.k,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable || keyObject.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "secret"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoGcm.exportKey = function ( p) {
|
|
|
|
var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);
|
|
|
|
return { type: "keyExport", keyHandle: jsonKeyStringArray };
|
|
};
|
|
|
|
operations.register("importKey", "aes-gcm", msrcryptoGcm.importKey);
|
|
operations.register("exportKey", "aes-gcm", msrcryptoGcm.exportKey);
|
|
operations.register("generateKey", "aes-gcm", msrcryptoGcm.generateKey);
|
|
operations.register("encrypt", "aes-gcm", msrcryptoGcm.encrypt);
|
|
operations.register("decrypt", "aes-gcm", msrcryptoGcm.decrypt);
|
|
} function MsrcryptoPrng() {
|
|
/// <summary>Pseudo Random Number Generator function/class.</summary>
|
|
/// <remarks>This is the PRNG engine, not the entropy collector.
|
|
/// The engine must be initialized with adequate entropy in order to generate cryptographically secure
|
|
/// random numbers. It is hard to get entropy, but see the entropy functoin/class for the entropy gatherer.
|
|
/// This is not an object instantiation, but the definition of the object. The actual
|
|
/// object must be instantiated somewhere else as needed.
|
|
/// </remarks>
|
|
|
|
if (!(this instanceof MsrcryptoPrng)) {
|
|
throw new Error("create MsrcryptoPrng object with new keyword");
|
|
}
|
|
|
|
// Fallback for browsers which do not implements crypto API yet
|
|
// implementation of http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf.
|
|
// Use AES-256 in CTR mode of operation as defined in Section 10.2.1.
|
|
var initialized = false;
|
|
|
|
// Internal state definitions are as follows.
|
|
// v : internal variable that will ultimately be the random output
|
|
// key: the AES key (256 bits)
|
|
// keyLen: the AES key length in bytes
|
|
// reseedCounter: the number of requests for pseudorandom bits since instantiation/reseeding
|
|
// reseedInterval: Maximum number of generate calls per seed or reseed. SP800-90A says 2^48 for AES, we use 2^24.
|
|
var key;
|
|
var v;
|
|
var keyLen;
|
|
var seedLen;
|
|
var reseedCounter = 1;
|
|
var reseedInterval = 1 << 24;
|
|
|
|
// Initialize this instance (constructor like function)
|
|
initialize();
|
|
|
|
function addOne(counter) {
|
|
/// <summary>Adds one to a big integer represented in an array (the first argument).</summary>
|
|
/// <param name="counter" counter="Array">The counter byte array to add one to encoded in big endian; index 0 is the MSW.</param>
|
|
var i;
|
|
for (i = counter.length - 1; i >= 0; i -= 1) {
|
|
counter[i] += 1;
|
|
if (counter[i] >= 256) {
|
|
counter[i] = 0;
|
|
}
|
|
if (counter[i]) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function initialize() {
|
|
/// <summary>Instantiate the PRNG with given entropy and personalization string.</summary>
|
|
/// <param name="entropy" type="Array">Array of bytes obtained from the source of entropy input.</param>
|
|
/// <param name="personalizationString" type="Array">Optional application-provided personalization string.</param>
|
|
key = msrcryptoUtilities.getVector(32);
|
|
v = msrcryptoUtilities.getVector(16); // AES block length
|
|
keyLen = 32;
|
|
seedLen = 48; // From SP800-90A, section 10.2.1 as of 2014.
|
|
reseedCounter = 1;
|
|
}
|
|
|
|
function reseed(entropy, additionalEntropy) {
|
|
/// <summary>Reseed the PRNG with additional entropy.</summary>
|
|
/// <param name="entropy" type="Array">Input entropy.</param>
|
|
/// <param name="additionalEntropy" type="Array">Optional additional entropy input.</param>
|
|
additionalEntropy = additionalEntropy || [0];
|
|
if (additionalEntropy.length > seedLen) {
|
|
throw new Error("Incorrect entropy or additionalEntropy length");
|
|
}
|
|
additionalEntropy = additionalEntropy.concat(msrcryptoUtilities.getVector(seedLen - additionalEntropy.length));
|
|
|
|
// Process the entropy input in blocks with the same additional entropy.
|
|
// This is equivalent to the caller chunking entropy in blocks and calling this function for each chunk.
|
|
entropy = entropy.concat(msrcryptoUtilities.getVector((seedLen - (entropy.length % seedLen)) % seedLen));
|
|
for (var i = 0; i < entropy.length; i += seedLen) {
|
|
var seedMaterial = msrcryptoUtilities.xorVectors(entropy.slice(i, i + seedLen), additionalEntropy);
|
|
update(seedMaterial);
|
|
}
|
|
reseedCounter = 1;
|
|
}
|
|
|
|
function update(providedData) {
|
|
/// <summary>Add the providedData to the internal entropy pool, and update internal state.</summary>
|
|
/// <param name="providedData" type="Array">Input to add to the internal entropy pool.</param>
|
|
var temp = [];
|
|
var blockCipher = new msrcryptoBlockCipher.aes(key);
|
|
while (temp.length < seedLen) {
|
|
addOne(v);
|
|
var toEncrypt = v.slice(0, 16);
|
|
var outputBlock = blockCipher.encrypt(toEncrypt); // AES-256
|
|
temp = temp.concat(outputBlock);
|
|
}
|
|
temp = msrcryptoUtilities.xorVectors(temp, providedData);
|
|
key = temp.slice(0, keyLen);
|
|
v = temp.slice(keyLen);
|
|
}
|
|
|
|
function generate(requestedBytes, additionalInput) {
|
|
/// <summary>Generate pseudo-random bits, and update the internal PRNG state.</summary>
|
|
/// <param name="requestedBytes" type="Number">Number of pseudorandom bytes to be returned.</param>
|
|
/// <param name="additionalInput" type="Array">Application-provided additional input array (optional).</param>
|
|
/// <returns>Generated pseudorandom bytes.</returns>
|
|
if (requestedBytes >= 65536) {
|
|
throw new Error("too much random requested");
|
|
}
|
|
if (reseedCounter > reseedInterval) {
|
|
throw new Error("Reseeding is required");
|
|
}
|
|
if (additionalInput && additionalInput.length > 0) {
|
|
while (additionalInput.length < seedLen) {
|
|
additionalInput = additionalInput.concat(msrcryptoUtilities.getVector(seedLen - additionalInput.length));
|
|
}
|
|
update(additionalInput);
|
|
} else {
|
|
additionalInput = msrcryptoUtilities.getVector(seedLen);
|
|
}
|
|
var temp = [];
|
|
var blockCipher = new msrcryptoBlockCipher.aes(key);
|
|
while (temp.length < requestedBytes) {
|
|
addOne(v);
|
|
var toEncrypt = v.slice(0, v.length);
|
|
var outputBlock = blockCipher.encrypt(toEncrypt);
|
|
temp = temp.concat(outputBlock);
|
|
}
|
|
temp = temp.slice(0, requestedBytes);
|
|
update(additionalInput);
|
|
reseedCounter += 1;
|
|
return temp;
|
|
}
|
|
|
|
return {
|
|
reseed: reseed,
|
|
/// <summary>Reseed the PRNG with additional entropy.</summary>
|
|
/// <param name="entropy" type="Array">Input entropy.</param>
|
|
/// <param name="additionalEntropy" type="Array">Optional additional entropy input.</param>
|
|
|
|
init: function (entropy, personalization) {
|
|
/// <summary>Initialize the PRNG by seeing with entropy and optional input data.</summary>
|
|
/// <param name="entropy" type="Array">Input entropy.</param>
|
|
/// <param name="personalization" type="Array">Optional input.</param>
|
|
if (entropy.length < seedLen) {
|
|
throw new Error("Initial entropy length too short");
|
|
}
|
|
initialize();
|
|
reseed(entropy, personalization);
|
|
initialized = true;
|
|
},
|
|
getBytes: function (length, additionalInput) {
|
|
if (!initialized) {
|
|
throw new Error("can't get randomness before initialization");
|
|
}
|
|
return generate(length, additionalInput);
|
|
},
|
|
getNonZeroBytes: function (length, additionalInput) {
|
|
if (!initialized) {
|
|
throw new Error("can't get randomness before initialization");
|
|
}
|
|
var result = [], buff;
|
|
while (result.length < length) {
|
|
buff = generate(length, additionalInput);
|
|
for (var i = 0 ; i < buff.length; i += 1) {
|
|
if (buff[i] !== 0) {
|
|
result.push(buff[i]);
|
|
}
|
|
}
|
|
}
|
|
return result.slice(0, length);
|
|
}
|
|
};
|
|
}
|
|
|
|
// This is the PRNG object per instantiation, including one per worker.
|
|
// The instance in the main thread is used to seed the instances in workers.
|
|
// TODO: Consider combining the entropy pool in the main thread with the PRNG instance in the main thread.
|
|
var msrcryptoPseudoRandom = new MsrcryptoPrng();
|
|
|
|
function MsrcryptoEntropy() {
|
|
/// <summary>Opportunistic entropy collector.</summary>
|
|
/// <remarks>See E.Stark, M.Hamburg, D.Boneh, "Symmetric Cryptography in Javascript", ACSAC, 2009.
|
|
/// This is not an object instantiation, but the definition of the object. The actual
|
|
/// object must be instantiated somewhere else as needed.
|
|
/// If window.{crypto,msCrypto}.getRandomValues() function is present, do not register mouse and JS load events,
|
|
/// because they slow down the execution, and it is not clear how much they contributed over and above
|
|
/// a cryptographic random value.
|
|
/// </remarks>
|
|
|
|
var poolLength = 48; // In bytes, from SP800-90A, Section 10.2.1. See random.js for constraints.
|
|
var collectorPool = [];
|
|
var collectorPoolLength = 128; // Bytes to collect before stopping; collectors are restartable.
|
|
var collectorsRegistered = 0;
|
|
var entropyPoolPrng = new MsrcryptoPrng();
|
|
var initialized = false;
|
|
var cryptographicPRNGPresent = false;
|
|
var headerList = ["Cookie", "RedirectUri", "ETag", "x-ms-client-antiforgery-id", "x-ms-client-request-id", "x-ms-client-session-id", "SubscriptionPool"];
|
|
|
|
function collectEntropy() {
|
|
/// <summary>Initialize the internal pool with as much randomness as one can get in JS.
|
|
/// In the worst case, there is zero bits of entropy.</summary>
|
|
|
|
var i, pool = [];
|
|
|
|
// In Safari, as of r39510, reportedly, Math.random() is cryptographically secure on Mac and Windows.
|
|
// Even if it isn't, mix that in via XORing into the existing array.
|
|
// According to ECMA, Math.random() returns [0,1). Thus, multiply it by 256 to get [0,256).
|
|
for (i = 0; i < poolLength; i += 1) {
|
|
pool[i] = Math.floor(Math.random() * 256);
|
|
}
|
|
|
|
// For browsers that implement window.crypto.getRandomValues, use it.
|
|
var prngCrypto = window.crypto || window.msCrypto; // WARNING: !!! Do not put this in a function (remember polyfill) !!!
|
|
if (prngCrypto && typeof prngCrypto.getRandomValues === "function") {
|
|
if (window.Uint8Array) {
|
|
var res = new window.Uint8Array(poolLength);
|
|
prngCrypto.getRandomValues(res);
|
|
pool = pool.concat(Array.apply(null, res));
|
|
cryptographicPRNGPresent = true;
|
|
}
|
|
}
|
|
|
|
// Read HTTP headers that contain entropy and reseed the entropy pool
|
|
var req = new XMLHttpRequest();
|
|
for (i = 0; i < headerList.length; i += 1) {
|
|
try {
|
|
var header = req.getResponseHeader(headerList[i]);
|
|
if (header) {
|
|
var arr = msrcryptoUtilities.stringToBytes(header);
|
|
pool = pool.concat(arr);
|
|
}
|
|
}
|
|
catch (err) {
|
|
// Ignore any header I can't get
|
|
}
|
|
}
|
|
|
|
if (!cryptographicPRNGPresent) {
|
|
// Add any data in the collector pool, empty the collector pool, and restart collectors.
|
|
pool = pool.concat(collectorPool.splice(0, collectorPool.length));
|
|
collectors.startCollectors();
|
|
}
|
|
|
|
// Worst case: initialized with Math.random()
|
|
initialized ? entropyPoolPrng.reseed(pool) : entropyPoolPrng.init(pool);
|
|
initialized = true;
|
|
}
|
|
|
|
function updatePool(entropyData) {
|
|
/// <summary>Collect the incoming data into the pool, and
|
|
/// empty the pool into the entropy PRNG state when the pool is full.
|
|
/// This function is additive entropy, only; this is not the main source of entropy.</summary>
|
|
/// <param name="entropyData" type="Array">Entropy input.</param>
|
|
for (var i = 0; i < entropyData.length; ++i) {
|
|
collectorPool.push(entropyData[i]);
|
|
}
|
|
if (collectorPool.length >= collectorPoolLength) {
|
|
// Stop the collectors (performance reasons).
|
|
// The real entropy does not come from the event callbacks: these are at best uniquifiers.
|
|
collectors.stopCollectors();
|
|
}
|
|
}
|
|
|
|
// Event listeners are not supported in IE 8.
|
|
// See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener?redirectlocale=en-US&redirectslug=DOM%2FEventTarget.addEventListener
|
|
// to add IE8 support.
|
|
// BUGBUG: For the time being, I am not bothering with IE8 support - fix this.
|
|
var collectors = (function () {
|
|
return {
|
|
startCollectors: function () {
|
|
if (!this.collectorsRegistered) {
|
|
if (window.addEventListener) {
|
|
window.addEventListener("mousemove", this.MouseEventCallBack, true);
|
|
window.addEventListener("load", this.LoadTimeCallBack, true);
|
|
} else if (document.attachEvent) {
|
|
document.attachEvent("onmousemove", this.MouseEventCallBack);
|
|
document.attachEvent("onload", this.LoadTimeCallBack);
|
|
} else {
|
|
throw new Error("Can't attach events for entropy collection");
|
|
}
|
|
|
|
this.collectorsRegistered = 1;
|
|
}
|
|
},
|
|
stopCollectors: function () {
|
|
if (this.collectorsRegistered) {
|
|
if (window.removeEventListener) {
|
|
window.removeEventListener("mousemove", this.MouseEventCallBack, 1);
|
|
window.removeEventListener("load", this.LoadTimeCallBack, 1);
|
|
} else if (window.detachEvent) {
|
|
window.detachEvent("onmousemove", this.MouseEventCallBack);
|
|
window.detachEvent("onload", this.LoadTimeCallBack);
|
|
}
|
|
|
|
this.collectorsRegistered = 0;
|
|
}
|
|
},
|
|
MouseEventCallBack: function (eventData) {
|
|
/// <summary>Add the mouse coordinates to the entropy pool and the Date.</summary>
|
|
/// <param name="eventData">Event data with mouse information.</param>
|
|
var d = (new Date()).valueOf();
|
|
var x = eventData.x || eventData.clientX || eventData.offsetX || 0;
|
|
var y = eventData.y || eventData.clientY || eventData.offsetY || 0;
|
|
var arr = [d & 0x0ff, (d >> 8) & 0x0ff, (d >> 16) & 0x0ff, (d >> 24) & 0x0ff,
|
|
x & 0x0ff, (x >> 8) & 0x0ff, y & 0x0ff, (y >> 8) & 0x0ff];
|
|
|
|
updatePool(arr);
|
|
},
|
|
LoadTimeCallBack: function () {
|
|
/// <summary>Add date to the entropy pool.</summary>
|
|
/// <remarks>Date valueOf() returns milliseconds since midnight 1/1/1970 UTC in a 32 bit integer</remarks>
|
|
var d = (new Date()).valueOf();
|
|
var dateArray = [d & 0x0ff, (d >> 8) & 0x0ff, (d >> 16) & 0x0ff, (d >> 24) & 0x0ff];
|
|
|
|
updatePool(dateArray);
|
|
}
|
|
};
|
|
})();
|
|
|
|
return {
|
|
init: function () {
|
|
collectEntropy();
|
|
|
|
// Register collectors
|
|
if (!cryptographicPRNGPresent && !collectorsRegistered) {
|
|
try {
|
|
collectors.startCollectors();
|
|
}
|
|
catch (e) {
|
|
// Ignore errors instead of trying to do something browser specific. That is not tractable.
|
|
// It is expected that the calling program injects most of the entropy or the build-in collectors
|
|
// contributes rather than registered events.
|
|
}
|
|
}
|
|
},
|
|
|
|
reseed: function (entropy) {
|
|
/// <summary>Mix in entropy into the pool.</summary>
|
|
/// <param name="entropy" type="Array">Entropy to mix in.</param>
|
|
entropyPoolPrng.reseed(entropy);
|
|
},
|
|
|
|
read: function (length) {
|
|
/// <summary>Read entropy from the entropy pool. This function fails if there isn't enough entropy.</summary>
|
|
/// <param name="length" type="Number">Number of bytes of requested entropy.</param>
|
|
/// <returns type="Array">Entropy if there is enough in the pool, or undefined if there isn't enough entropy.</returns>
|
|
if (!initialized) {
|
|
throw new Error("Entropy pool is not initialized.");
|
|
}
|
|
|
|
var ret = entropyPoolPrng.getBytes(length);
|
|
|
|
// TODO: Do this async?
|
|
// No, another call may come through before the pool is reseeded.
|
|
// All PRNGs have their own running state anyhow. They can reseed themselves in async mode, if need be.
|
|
collectEntropy();
|
|
|
|
return ret;
|
|
}
|
|
};
|
|
}
|
|
|
|
var msrcryptoRsaBase = function (keyStruct) {
|
|
|
|
var utils = msrcryptoUtilities,
|
|
keyIsPrivate = keyStruct.hasOwnProperty("n") && keyStruct.hasOwnProperty("d"),
|
|
keyIsCrt = keyStruct.hasOwnProperty("p") && keyStruct.hasOwnProperty("q"),
|
|
modulusLength = keyStruct.n.length;
|
|
|
|
function toBytes(digits) {
|
|
|
|
var bytes = cryptoMath.digitsToBytes(digits);
|
|
|
|
// Add leading zeros until the message is the proper length.
|
|
utils.padFront(bytes, 0, modulusLength);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
function modExp( dataBytes, expBytes, modulusBytes) {
|
|
/// <returns type="Array">Result in a digit array.</returns>
|
|
var exponent = cryptoMath.bytesToDigits(expBytes);
|
|
|
|
var group = cryptoMath.IntegerGroup(modulusBytes);
|
|
var base = group.createElementFromBytes(dataBytes);
|
|
var result = group.modexp(base, exponent);
|
|
|
|
return result.m_digits;
|
|
}
|
|
|
|
function decryptModExp(cipherBytes) {
|
|
|
|
var resultElement = modExp(cipherBytes, keyStruct.d, keyStruct.n);
|
|
|
|
return toBytes(resultElement);
|
|
}
|
|
|
|
function decryptCrt(cipherBytes) {
|
|
|
|
var p = keyStruct.p,
|
|
q = keyStruct.q,
|
|
dp = keyStruct.dp,
|
|
dq = keyStruct.dq,
|
|
invQ = keyStruct.qi,
|
|
pDigits = cryptoMath.bytesToDigits(p),
|
|
qDigits = cryptoMath.bytesToDigits(q),
|
|
temp = new Array(pDigits.length + qDigits.length),
|
|
m1Digits = new Array(pDigits.length + 1),
|
|
m2Digits = new Array(qDigits.length + 1),
|
|
cDigits = cryptoMath.bytesToDigits(cipherBytes);
|
|
|
|
// 'm1' = (c mod p)^dP mod p
|
|
cryptoMath.reduce(cDigits, pDigits, temp);
|
|
cryptoMath.modExp(temp, cryptoMath.bytesToDigits(dp), pDigits, m1Digits);
|
|
|
|
// 'm2' = (c mod q)^dQ mod q
|
|
cryptoMath.reduce(cDigits, qDigits, temp);
|
|
cryptoMath.modExp(temp, cryptoMath.bytesToDigits(dq), qDigits, m2Digits);
|
|
|
|
// 'diff' = (m1 - m2). Compute as follows to have |m1 - m2|.
|
|
// m1 - m2 if m1>=m2.
|
|
// m2 - m1 if m1<m2.
|
|
// Correct the sign after modular multiplication by qInv by subtracting the product from p.
|
|
var carry = cryptoMath.subtract(m1Digits, m2Digits, temp);
|
|
if (carry !== 0) {
|
|
cryptoMath.subtract(m2Digits, m1Digits, temp);
|
|
}
|
|
|
|
// 'h' = (m1 - m2)^qInv mod p
|
|
cryptoMath.modMul(temp, cryptoMath.bytesToDigits(invQ), pDigits, cDigits);
|
|
if (carry !== 0) {
|
|
cryptoMath.subtract(pDigits, cDigits, cDigits);
|
|
}
|
|
|
|
// 'm2' + q*h
|
|
cryptoMath.multiply(cDigits, qDigits, temp);
|
|
cryptoMath.add(m2Digits, temp, m1Digits);
|
|
|
|
return toBytes(m1Digits);
|
|
}
|
|
|
|
return {
|
|
|
|
encrypt: function (messageBytes) {
|
|
|
|
return toBytes(modExp(messageBytes, keyStruct.e, keyStruct.n));
|
|
|
|
},
|
|
|
|
decrypt: function (cipherBytes) {
|
|
|
|
if (keyIsCrt) {
|
|
return decryptCrt(cipherBytes);
|
|
}
|
|
|
|
if (keyIsPrivate) {
|
|
return decryptModExp(cipherBytes);
|
|
}
|
|
|
|
throw new Error("missing private key");
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
var rsaShared = {
|
|
|
|
mgf1: function (seedBytes, maskLen, hashFunction) {
|
|
|
|
var t = [], bytes, hash, counter,
|
|
hashByteLen = hashFunction.hashLen / 8;
|
|
|
|
for (counter = 0; counter <= Math.floor(maskLen / hashByteLen) ; counter += 1) {
|
|
|
|
bytes = [counter >>> 24 & 0xff,
|
|
counter >>> 16 & 0xff,
|
|
counter >>> 8 & 0xff,
|
|
counter & 0xff];
|
|
|
|
hash = hashFunction.computeHash(seedBytes.concat(bytes));
|
|
|
|
t = t.concat(hash);
|
|
}
|
|
|
|
return t.slice(0, maskLen);
|
|
},
|
|
|
|
checkMessageVsMaxHash: function (messageBytes, hashFunction) {
|
|
|
|
// The max array size in JS is 2^32-1
|
|
if (messageBytes.length > (hashFunction.maxMessageSize || 0xFFFFFFFF)) {
|
|
throw new Error("message too long");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
};
|
|
|
|
var rsaMode = rsaMode || {};
|
|
|
|
rsaMode.oaep = function (keyStruct, hashFunction) {
|
|
|
|
var utils = msrcryptoUtilities,
|
|
random = msrcryptoPseudoRandom,
|
|
size = keyStruct.n.length;
|
|
|
|
if (hashFunction === null) {
|
|
throw new Error("must supply hashFunction");
|
|
}
|
|
|
|
function pad( message, label) {
|
|
|
|
var lHash, psLen, psArray, i, db, seed;
|
|
var dbMask, maskeddb, seedMask, maskedSeed;
|
|
var encodedMessage;
|
|
|
|
if (message.length > (size - 2 * (hashFunction.hashLen / 8) - 2)) {
|
|
throw new Error("Message too long.");
|
|
}
|
|
|
|
label || (label = []);
|
|
|
|
lHash = hashFunction.computeHash( label);
|
|
|
|
psLen = size - message.length - (2 * lHash.length) - 2;
|
|
psArray = utils.getVector(psLen);
|
|
|
|
// 'db' = 'lHash' || 'psArray' || 0x01 || message
|
|
db = lHash.concat(psArray, [1], message);
|
|
|
|
seed = random.getBytes(lHash.length);
|
|
|
|
dbMask = rsaShared.mgf1(seed, size - lHash.length - 1, hashFunction);
|
|
|
|
maskeddb = utils.xorVectors(db, dbMask);
|
|
|
|
seedMask = rsaShared.mgf1(maskeddb, lHash.length, hashFunction);
|
|
|
|
maskedSeed = utils.xorVectors(seed, seedMask);
|
|
|
|
encodedMessage = [0].concat(maskedSeed).concat(maskeddb);
|
|
|
|
message = encodedMessage.slice();
|
|
|
|
return encodedMessage;
|
|
}
|
|
|
|
function unpad( encodedBytes, labelBytes) {
|
|
|
|
var lHash, maskedSeed, maskeddb, seedMask;
|
|
var seed, dbMask, db;
|
|
var lHashp;
|
|
|
|
if (!labelBytes) {
|
|
labelBytes = [];
|
|
}
|
|
|
|
lHash = hashFunction.computeHash(labelBytes);
|
|
|
|
if (encodedBytes[0] !== 0) {
|
|
throw new Error("Encryption Error");
|
|
}
|
|
|
|
maskedSeed = encodedBytes.slice(1, lHash.length + 1);
|
|
maskeddb = encodedBytes.slice(lHash.length + 1);
|
|
|
|
seedMask = rsaShared.mgf1(maskeddb, lHash.length, hashFunction);
|
|
seed = utils.xorVectors(maskedSeed, seedMask);
|
|
dbMask = rsaShared.mgf1(seed, size - lHash.length - 1, hashFunction);
|
|
|
|
db = utils.xorVectors(maskeddb, dbMask);
|
|
|
|
lHashp = db.slice(0, lHash.length);
|
|
|
|
// lHashp should equal lHash or 'Encryption Error'
|
|
if (!utils.arraysEqual(lHash, lHashp)) {
|
|
throw new Error("Encryption Error");
|
|
}
|
|
|
|
db = db.slice(lHash.length);
|
|
|
|
// There will be a bunch of zeros followed by a
|
|
var i = db.indexOf(1);
|
|
|
|
return db.slice(i + 1);
|
|
}
|
|
|
|
return {
|
|
|
|
pad: function ( messageBytes, labelBytes) {
|
|
return pad(messageBytes, labelBytes);
|
|
},
|
|
|
|
unpad: function ( encodedBytes, labelBytes) {
|
|
return unpad(encodedBytes, labelBytes);
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
var rsaMode = rsaMode || {};
|
|
|
|
rsaMode.pkcs1Encrypt = function (keyStruct) {
|
|
|
|
var random = msrcryptoPseudoRandom,
|
|
size = keyStruct.n.length;
|
|
|
|
function pad(data) {
|
|
|
|
var randomness;
|
|
|
|
if (data.length > size - 11) {
|
|
throw new Error("message too long");
|
|
}
|
|
|
|
// A minimum of 8 random bytes
|
|
randomness = random.getNonZeroBytes(size - data.length - 3);
|
|
|
|
return [0, 2].concat(randomness, [0], data);
|
|
}
|
|
|
|
function unpad(paddedData) {
|
|
var i;
|
|
|
|
for (i = 1; i < paddedData.length; i += 1) {
|
|
if (paddedData[i] === 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return paddedData.slice(i + 1);
|
|
}
|
|
|
|
return {
|
|
|
|
pad: function (messageBytes) {
|
|
return pad(messageBytes);
|
|
},
|
|
|
|
unpad: function (encodedBytes) {
|
|
return unpad(encodedBytes);
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
rsaMode.pkcs1Sign = function (keyStruct, hashFunction) {
|
|
|
|
var utils = msrcryptoUtilities,
|
|
size = keyStruct.n.length;
|
|
|
|
function emsa_pkcs1_v15_encode(messageBytes) {
|
|
|
|
var paddedData,
|
|
hash,
|
|
tlen;
|
|
|
|
hash = hashFunction.computeHash(messageBytes.slice());
|
|
|
|
paddedData = hashFunction.der.concat(hash);
|
|
|
|
tlen = paddedData.length;
|
|
|
|
if (size < tlen + 11) {
|
|
throw new Error("intended encoded message length too short");
|
|
}
|
|
|
|
return [0x00, 0x01].concat(
|
|
utils.getVector(size - tlen - 3, 0xFF),
|
|
[0],
|
|
paddedData);
|
|
}
|
|
|
|
return {
|
|
|
|
sign: function (messageBytes) {
|
|
return emsa_pkcs1_v15_encode(messageBytes);
|
|
},
|
|
|
|
verify: function (signatureBytes, messageBytes) {
|
|
var emp = emsa_pkcs1_v15_encode(messageBytes);
|
|
|
|
return utils.arraysEqual(signatureBytes, emp);
|
|
|
|
}
|
|
};
|
|
};
|
|
|
|
var rsaMode = rsaMode || {};
|
|
|
|
rsaMode.pss = function (keyStruct, hashFunction) {
|
|
|
|
var utils = msrcryptoUtilities,
|
|
random = msrcryptoPseudoRandom;
|
|
|
|
function emsa_pss_encode(messageBytes, saltLength, salt) {
|
|
|
|
var emBits = (keyStruct.n.length * 8) - 1,
|
|
emLen = Math.ceil(emBits / 8), mHash = hashFunction.computeHash(messageBytes);
|
|
|
|
saltLength = salt ? salt.length : saltLength || mHash.length;
|
|
|
|
if (emLen < (mHash.length + saltLength + 2)) {
|
|
throw new Error("encoding error");
|
|
} salt = salt || random.getBytes(saltLength);
|
|
|
|
// M' = (0x) 00 00 00 00 00 00 00 00 || mHash || salt
|
|
var mp = [0, 0, 0, 0, 0, 0, 0, 0].concat(mHash, salt);
|
|
|
|
var h = hashFunction.computeHash(mp);
|
|
|
|
var ps = utils.getVector(emLen - salt.length - h.length - 2);
|
|
|
|
var db = ps.concat([1], salt);
|
|
|
|
var dbMask = rsaShared.mgf1(h, emLen - h.length - 1, hashFunction);
|
|
|
|
var maskedDb = utils.xorVectors(db, dbMask);
|
|
|
|
// Set the ((8 * emLen) - emBits) of the leftmost octect in maskedDB to zero
|
|
var mask = 0;
|
|
for (var i = 0; i < 8 - ((8 * emLen) - emBits) ; i++) {
|
|
mask += 1 << i;
|
|
}
|
|
maskedDb[0] &= mask;
|
|
|
|
var em = maskedDb.concat(h, [0xbc]);
|
|
|
|
return em;
|
|
}
|
|
|
|
function emsa_pss_verify( signatureBytes, messageBytes, saltLength) {
|
|
|
|
var emBits = (keyStruct.n.length * 8) - 1;
|
|
|
|
var emLen = Math.ceil(emBits / 8);
|
|
|
|
var mHash = hashFunction.computeHash(messageBytes);
|
|
|
|
var hLen = mHash.length;
|
|
|
|
saltLength = saltLength || hLen;
|
|
|
|
if (emLen < (hLen + saltLength + 2)) {
|
|
return false;
|
|
}
|
|
|
|
var maskedDb = signatureBytes.slice(0, emLen - hLen - 1);
|
|
|
|
var h = signatureBytes.slice(maskedDb.length, maskedDb.length + hLen);
|
|
|
|
var dbMask = rsaShared.mgf1(h, emLen - hLen - 1, hashFunction);
|
|
|
|
var db = utils.xorVectors(maskedDb, dbMask);
|
|
|
|
// Set the leftmost 8 * emLen - emBits of db[0] to zero
|
|
db[0] &= 0xFF >>> (8 - ((8 * emLen) - emBits));
|
|
|
|
// Verify the leftmost bytes are zero
|
|
for (var i = 0; i < (emLen - hLen - saltLength - 2) ; i++) {
|
|
if (db[i] !== 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (db[emLen - hLen - saltLength - 2] !== 0x01) {
|
|
return false;
|
|
}
|
|
|
|
var salt = db.slice(-saltLength);
|
|
|
|
// M' = (0x) 00 00 00 00 00 00 00 00 || mHash || salt
|
|
var mp = [0, 0, 0, 0, 0, 0, 0, 0].concat(mHash, salt);
|
|
|
|
var hp = hashFunction.computeHash(mp);
|
|
|
|
return utils.arraysEqual(hp, h);
|
|
}
|
|
|
|
return {
|
|
|
|
sign: function (messageBytes, saltLength, salt) {
|
|
return emsa_pss_encode(messageBytes, saltLength, salt);
|
|
},
|
|
|
|
verify: function (signatureBytes, messageBytes, saltLength) {
|
|
return emsa_pss_verify(signatureBytes, messageBytes, saltLength);
|
|
}
|
|
};
|
|
};
|
|
|
|
var msrcryptoRsa = function (keyStruct, mode, hashFunction) {
|
|
|
|
var rsaBase = msrcryptoRsaBase(keyStruct);
|
|
|
|
if (!mode) {
|
|
throw new Error("padding mode");
|
|
}
|
|
|
|
function checkHash() {
|
|
if (!hashFunction || !hashFunction.computeHash) {
|
|
throw new Error("missing hash function");
|
|
}
|
|
}
|
|
|
|
var paddingFunction = null,
|
|
unPaddingFunction = null;
|
|
|
|
var padding;
|
|
|
|
switch (mode) {
|
|
|
|
case "rsaes-pkcs1-v1_5":
|
|
padding = rsaMode.pkcs1Encrypt(keyStruct);
|
|
break;
|
|
|
|
case "rsassa-pkcs1-v1_5":
|
|
checkHash();
|
|
padding = rsaMode.pkcs1Sign(keyStruct, hashFunction);
|
|
break;
|
|
|
|
case "rsa-oaep":
|
|
checkHash();
|
|
padding = rsaMode.oaep(keyStruct, hashFunction);
|
|
break;
|
|
|
|
case "rsa-pss":
|
|
checkHash();
|
|
padding = rsaMode.pss(keyStruct, hashFunction);
|
|
break;
|
|
|
|
case "raw":
|
|
padding = {
|
|
pad: function (mb) { return mb; },
|
|
unpad: function (eb) { return eb; }
|
|
};
|
|
break;
|
|
|
|
default:
|
|
throw new Error("invalid mode");
|
|
}
|
|
|
|
if (padding) {
|
|
paddingFunction = padding.pad || padding.sign;
|
|
unPaddingFunction = padding.unpad || padding.verify;
|
|
}
|
|
|
|
var returnObj = {
|
|
|
|
encrypt: function ( dataBytes, labelBytes) {
|
|
|
|
var paddedData;
|
|
|
|
if (paddingFunction !== null) {
|
|
// OAEP padding can take two arguments
|
|
paddedData = paddingFunction(dataBytes, labelBytes);
|
|
} else {
|
|
// Slice() has optional arguments
|
|
paddedData = dataBytes.slice();
|
|
}
|
|
|
|
return rsaBase.encrypt(paddedData);
|
|
},
|
|
|
|
decrypt: function ( cipherBytes, labelBytes) {
|
|
|
|
var decryptedData = rsaBase.decrypt(cipherBytes);
|
|
|
|
if (unPaddingFunction !== null) {
|
|
// OAEP padding can take two arguments
|
|
decryptedData = unPaddingFunction(decryptedData, labelBytes);
|
|
} else {
|
|
decryptedData = decryptedData.slice(0);
|
|
}
|
|
|
|
return decryptedData;
|
|
},
|
|
|
|
signData: function ( messageBytes, saltLength, salt) {
|
|
|
|
return rsaBase.decrypt(paddingFunction(messageBytes, saltLength, salt));
|
|
},
|
|
|
|
verifySignature: function ( signature, messageBytes, saltLength) {
|
|
|
|
var decryptedSig = rsaBase.encrypt(signature);
|
|
|
|
return unPaddingFunction(decryptedSig, messageBytes, saltLength);
|
|
},
|
|
|
|
mode: mode
|
|
};
|
|
|
|
return returnObj;
|
|
};
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoRsa.sign = function ( p) {
|
|
|
|
var rsaObj,
|
|
hashName = p.algorithm.hash.name,
|
|
hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
|
|
saltLength = p.algorithm.saltLength,
|
|
salt = p.algorithm.salt;
|
|
|
|
rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);
|
|
|
|
return rsaObj.signData(p.buffer, saltLength, salt);
|
|
};
|
|
|
|
msrcryptoRsa.verify = function ( p) {
|
|
|
|
var hashName = p.algorithm.hash.name,
|
|
hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
|
|
rsaObj,
|
|
saltLength = p.algorithm.saltLength;
|
|
|
|
rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);
|
|
|
|
return rsaObj.verifySignature(p.signature, p.buffer, saltLength);
|
|
};
|
|
|
|
msrcryptoRsa.workerEncrypt = function ( p) {
|
|
|
|
var result,
|
|
rsaObj,
|
|
hashFunc,
|
|
hashName;
|
|
|
|
switch (p.algorithm.name) {
|
|
|
|
case "rsaes-pkcs1-v1_5":
|
|
rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name);
|
|
result = rsaObj.encrypt(p.buffer);
|
|
break;
|
|
|
|
case "rsa-oaep":
|
|
hashName = p.algorithm.hash.name;
|
|
if (!hashName) {
|
|
throw new Error("unsupported hash algorithm");
|
|
}
|
|
hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()];
|
|
rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);
|
|
result = rsaObj.encrypt(p.buffer);
|
|
break;
|
|
|
|
default:
|
|
throw new Error("unsupported algorithm");
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
msrcryptoRsa.workerDecrypt = function ( p) {
|
|
|
|
var result,
|
|
rsaObj,
|
|
hashFunc;
|
|
|
|
switch (p.algorithm.name) {
|
|
|
|
case "rsaes-pkcs1-v1_5":
|
|
rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name);
|
|
result = rsaObj.decrypt(p.buffer);
|
|
break;
|
|
|
|
case "rsa-oaep":
|
|
var hashName = p.algorithm.hash.name;
|
|
if (!hashName) {
|
|
throw new Error("unsupported hash algorithm");
|
|
}
|
|
hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()];
|
|
rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);
|
|
result = rsaObj.decrypt(p.buffer);
|
|
break;
|
|
|
|
default:
|
|
throw new Error("unsupported algorithm");
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
msrcryptoRsa.importKey = function ( p) {
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["n", "e", "d", "q", "p", "dq", "dp", "qi"]);
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable || keyObject.extractable,
|
|
keyUsage: null || p.keyUsage, // IE11 returns null here
|
|
type: (keyObject.d || keyObject.dq) ? "private" : "public"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoRsa.exportKey = function ( p) {
|
|
|
|
var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);
|
|
|
|
return { type: "keyExport", keyHandle: jsonKeyStringArray };
|
|
};
|
|
|
|
operations.register("sign", "rsassa-pkcs1-v1_5", msrcryptoRsa.sign);
|
|
operations.register("sign", "rsa-pss", msrcryptoRsa.sign);
|
|
|
|
operations.register("verify", "rsassa-pkcs1-v1_5", msrcryptoRsa.verify);
|
|
operations.register("verify", "rsa-pss", msrcryptoRsa.verify);
|
|
|
|
operations.register("encrypt", "rsa-oaep", msrcryptoRsa.workerEncrypt);
|
|
operations.register("encrypt", "rsaes-pkcs1-v1_5", msrcryptoRsa.workerEncrypt);
|
|
|
|
operations.register("decrypt", "rsa-oaep", msrcryptoRsa.workerDecrypt);
|
|
operations.register("decrypt", "rsaes-pkcs1-v1_5", msrcryptoRsa.workerDecrypt);
|
|
|
|
operations.register("importKey", "rsa-oaep", msrcryptoRsa.importKey);
|
|
operations.register("importKey", "rsaes-pkcs1-v1_5", msrcryptoRsa.importKey);
|
|
operations.register("importKey", "rsassa-pkcs1-v1_5", msrcryptoRsa.importKey);
|
|
operations.register("importKey", "rsa-pss", msrcryptoRsa.importKey);
|
|
|
|
operations.register("exportKey", "rsa-oaep", msrcryptoRsa.exportKey);
|
|
operations.register("exportKey", "rsaes-pkcs1-v1_5", msrcryptoRsa.exportKey);
|
|
operations.register("exportKey", "rsassa-pkcs1-v1_5", msrcryptoRsa.exportKey);
|
|
operations.register("exportKey", "rsa-pss", msrcryptoRsa.exportKey);
|
|
|
|
}
|
|
|
|
/// The "concat" key derivation function from NIST SP-800-56A.
|
|
var msrcryptoKdf = function (hashFunction) {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
function deriveKey(secretBytes, otherInfo, keyOutputLength) {
|
|
/// <summary></summary>
|
|
/// <param name="secretBytes" type="Array"></param>
|
|
/// <param name="otherInfo" type="Array"></param>
|
|
/// <param name="keyOutputLength" type="Number"></param>
|
|
/// <returns type="Array"></returns>
|
|
|
|
var reps = Math.ceil(keyOutputLength / (hashFunction.hashLen / 8)),
|
|
counter = 1,
|
|
digest = secretBytes.concat(otherInfo),
|
|
output = [];
|
|
|
|
for (var i = 0; i < reps; i++) {
|
|
|
|
var data = utils.int32ToBytes(counter++).concat(digest);
|
|
|
|
var /*type(Array)*/ h = hashFunction.computeHash(data);
|
|
|
|
output = output.concat(h);
|
|
}
|
|
|
|
return output.slice(0, keyOutputLength);
|
|
}
|
|
|
|
return {
|
|
|
|
deriveKey: deriveKey
|
|
|
|
};
|
|
|
|
};
|
|
|
|
var msrcryptoKdfInstance = null;
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoKdf.deriveKey = function ( p) {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
var hashName = p.algorithm.hash.name;
|
|
|
|
var hashFunction = msrcryptoHashFunctions[hashName.toLowerCase()];
|
|
|
|
msrcryptoKdfInstance = msrcryptoKdf(hashFunction);
|
|
|
|
var alg = p.algorithm;
|
|
|
|
var otherInfo =
|
|
utils.toArray(alg.algorithmId).concat(
|
|
utils.toArray(alg.partyUInfo),
|
|
utils.toArray(alg.partyVInfo),
|
|
utils.toArray(alg.publicInfo),
|
|
utils.toArray(alg.privateInfo));
|
|
|
|
var result =
|
|
msrcryptoKdfInstance.deriveKey(p.keyData, otherInfo, p.derivedKeyType.length);
|
|
|
|
msrcryptoKdfInstance = null;
|
|
|
|
return {
|
|
type: "keyDerive",
|
|
keyData: result,
|
|
keyHandle: {
|
|
algorithm: p.derivedKeyType,
|
|
extractable: p.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "secret"
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
msrcryptoKdf.deriveBits = function ( p) {
|
|
|
|
var hashName = p.algorithm.hash.name;
|
|
|
|
var hashFunction = msrcryptoHashFunctions[hashName.toLowerCase()];
|
|
|
|
msrcryptoKdfInstance = msrcryptoKdf(hashFunction);
|
|
|
|
var alg = p.algorithm;
|
|
|
|
var otherInfo =
|
|
alg.algorithmId.concat(
|
|
alg.partyUInfo,
|
|
alg.partyVInfo,
|
|
alg.publicInfo || [],
|
|
alg.privateInfo || []);
|
|
|
|
var result =
|
|
msrcryptoKdfInstance.deriveKey(p.keyData, otherInfo, p.length);
|
|
|
|
msrcryptoKdfInstance = null;
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
operations.register("deriveKey", "concat", msrcryptoKdf.deriveKey);
|
|
operations.register("deriveBits", "concat", msrcryptoKdf.deriveBits);
|
|
|
|
}
|
|
|
|
var msrcryptoEcdh = function (curve) {
|
|
|
|
var btd = cryptoMath.bytesToDigits,
|
|
dtb = cryptoMath.digitsToBytes,
|
|
e = curve,
|
|
ecop = new cryptoECC.EllipticCurveOperatorFp(curve);
|
|
|
|
function generateKey(privateKeyBytes) {
|
|
/// <summary></summary>
|
|
/// <param name="privateKeyBytes" type="Array" optional="true">
|
|
/// For testing purposes we allow the key bytes to be passed in
|
|
/// instead of randomly generated.
|
|
/// </param>
|
|
/// <returns type=""></returns>
|
|
|
|
var privateKey = [],
|
|
randomBytes = msrcryptoPseudoRandom.getBytes(
|
|
curve.order.length * cryptoMath.DIGIT_NUM_BYTES);
|
|
|
|
cryptoMath.reduce(
|
|
cryptoMath.bytesToDigits(randomBytes),
|
|
e.order,
|
|
privateKey);
|
|
|
|
var publicKey = e.allocatePointStorage();
|
|
|
|
ecop.scalarMultiply(privateKey, e.generator, publicKey);
|
|
|
|
return {
|
|
privateKey: {
|
|
x: dtb(publicKey.x),
|
|
y: dtb(publicKey.y),
|
|
d: dtb(privateKey)
|
|
},
|
|
publicKey: {
|
|
x: dtb(publicKey.x),
|
|
y: dtb(publicKey.y)
|
|
}
|
|
};
|
|
}
|
|
|
|
function deriveBits(privateKey, publicKey, length) {
|
|
|
|
var publicPoint = new cryptoECC.EllipticCurvePointFp(
|
|
e, false, btd(publicKey.x), btd(publicKey.y), null, false);
|
|
|
|
var sharedSecretPoint = e.allocatePointStorage();
|
|
ecop.convertToJacobianForm(sharedSecretPoint);
|
|
ecop.convertToMontgomeryForm(sharedSecretPoint);
|
|
|
|
ecop.scalarMultiply(btd(privateKey.d), publicPoint, sharedSecretPoint);
|
|
|
|
ecop.convertToAffineForm(sharedSecretPoint);
|
|
ecop.convertToStandardForm(sharedSecretPoint);
|
|
|
|
var secretBytes = cryptoMath.digitsToBytes(sharedSecretPoint.x);
|
|
|
|
if (length && (secretBytes.length * 8) < length) {
|
|
throw new Error("DataError");
|
|
}
|
|
|
|
return length ? secretBytes.slice(0, length / 8) : secretBytes;
|
|
}
|
|
|
|
function computePublicKey(privateKeyBytes) {
|
|
|
|
if (!e.generator.isInMontgomeryForm) {
|
|
ecop.convertToMontgomeryForm(e.generator);
|
|
}
|
|
|
|
var publicKey = e.allocatePointStorage();
|
|
ecop.convertToJacobianForm(publicKey);
|
|
ecop.convertToMontgomeryForm(publicKey);
|
|
ecop.scalarMultiply(btd(privateKeyBytes), e.generator, publicKey);
|
|
|
|
return {
|
|
x: dtb(publicKey.x),
|
|
y: dtb(publicKey.y)
|
|
};
|
|
}
|
|
|
|
return {
|
|
|
|
generateKey: generateKey,
|
|
deriveBits: deriveBits,
|
|
computePublicKey: computePublicKey
|
|
};
|
|
|
|
};
|
|
|
|
var ecdhInstance = null;
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoEcdh.deriveBits = function (p) {
|
|
|
|
var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());
|
|
|
|
var privateKey = p.keyData;
|
|
|
|
var publicKey = p.additionalKeyData;
|
|
|
|
ecdhInstance = msrcryptoEcdh(curve);
|
|
|
|
var secretBytes = ecdhInstance.deriveBits(privateKey, publicKey, p.length);
|
|
|
|
return secretBytes;
|
|
};
|
|
|
|
msrcryptoEcdh.generateKey = function (p) {
|
|
|
|
var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());
|
|
|
|
ecdhInstance = msrcryptoEcdh(curve);
|
|
|
|
var keyPairData = ecdhInstance.generateKey();
|
|
|
|
return {
|
|
type: "keyPairGeneration",
|
|
keyPair: {
|
|
publicKey: {
|
|
keyData: keyPairData.publicKey,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: p.keyUsage,
|
|
type: "public"
|
|
}
|
|
},
|
|
privateKey: {
|
|
keyData: keyPairData.privateKey,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: p.keyUsage,
|
|
type: "private"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoEcdh.importKey = function (p) {
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["x", "y", "d", "crv"]);
|
|
|
|
// If only private key data 'd' is imported, create x and y
|
|
if (keyObject.d && (!keyObject.x || !keyObject.y)) {
|
|
|
|
var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());
|
|
|
|
ecdhInstance = msrcryptoEcdh(curve);
|
|
|
|
var publicKey = ecdhInstance.computePublicKey(keyObject.d);
|
|
|
|
keyObject.x = publicKey.x;
|
|
keyObject.y = publicKey.y;
|
|
}
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable || keyObject.extractable,
|
|
keyUsage: p.keyUsage,
|
|
type: (keyObject.d) ? "private" : "public"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoEcdh.exportKey = function (p) {
|
|
|
|
var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);
|
|
|
|
return { type: "keyExport", keyHandle: jsonKeyStringArray };
|
|
|
|
};
|
|
|
|
operations.register("importKey", "ecdh", msrcryptoEcdh.importKey);
|
|
operations.register("exportKey", "ecdh", msrcryptoEcdh.exportKey);
|
|
operations.register("generateKey", "ecdh", msrcryptoEcdh.generateKey);
|
|
operations.register("deriveBits", "ecdh", msrcryptoEcdh.deriveBits);
|
|
}
|
|
|
|
var msrcryptoEcdsa = function (curve) {
|
|
|
|
var btd = cryptoMath.bytesToDigits,
|
|
dtb = cryptoMath.digitsToBytes,
|
|
ecop = new cryptoECC.EllipticCurveOperatorFp(curve),
|
|
orderByteLength = dtb(curve.order).length,
|
|
tedCurve = (curve.type === 1);
|
|
|
|
function createKey(privateKeyBytes) {
|
|
return createKeyInternal(btd(privateKeyBytes));
|
|
}
|
|
|
|
function createKeyInternal(privateKeyDigits) {
|
|
|
|
var publicKey = curve.allocatePointStorage();
|
|
|
|
ecop.scalarMultiply(privateKeyDigits, curve.generator, publicKey);
|
|
|
|
return {
|
|
publicKey: publicKey,
|
|
privateKey: privateKeyDigits
|
|
};
|
|
}
|
|
|
|
function generateKey(randomBytes) {
|
|
|
|
var privateKey = [];
|
|
|
|
if (!randomBytes) {
|
|
randomBytes = msrcryptoPseudoRandom.getBytes(
|
|
curve.order.length * cryptoMath.DIGIT_NUM_BYTES);
|
|
}
|
|
|
|
cryptoMath.reduce(
|
|
cryptoMath.bytesToDigits(randomBytes),
|
|
curve.order,
|
|
privateKey);
|
|
|
|
return createKeyInternal(privateKey);
|
|
}
|
|
|
|
function getDigest(messageBytes) {
|
|
|
|
// if the message length is longer than the curve-order, truncate.
|
|
if (messageBytes.length > orderByteLength) {
|
|
messageBytes.length = orderByteLength;
|
|
}
|
|
|
|
var digest = btd(messageBytes);
|
|
|
|
if (tedCurve) {
|
|
var shift = (8 - curve.rbits % 8);
|
|
cryptoMath.shiftRight(digest, digest, shift);
|
|
}
|
|
|
|
cryptoMath.reduce(digest, curve.order, digest);
|
|
|
|
return digest;
|
|
}
|
|
|
|
function sign(privateKey, messageBytes, ephemeralKey) {
|
|
|
|
if (!ephemeralKey) {
|
|
ephemeralKey = generateKey();
|
|
}
|
|
|
|
var r = ephemeralKey.publicKey.x,
|
|
k = ephemeralKey.privateKey,
|
|
d = btd(privateKey.d),
|
|
digest = getDigest(messageBytes.slice()),
|
|
s = [],
|
|
tmp = [],
|
|
signature = null;
|
|
|
|
cryptoMath.reduce(r, curve.order, r);
|
|
cryptoMath.modMul(r, d, curve.order, s);
|
|
cryptoMath.add(s, digest, s);
|
|
cryptoMath.reduce(s, curve.order, s);
|
|
cryptoMath.modInv(k, curve.order, tmp);
|
|
cryptoMath.modMul(s, tmp, curve.order, s);
|
|
|
|
// ensure the bytes arrays are of the expected size
|
|
var rBytes = msrcryptoUtilities.padFront(dtb(r, true, orderByteLength), 0, orderByteLength);
|
|
var sBytes = msrcryptoUtilities.padFront(dtb(s, true, orderByteLength), 0, orderByteLength);
|
|
signature = rBytes.concat(sBytes);
|
|
|
|
return signature;
|
|
}
|
|
|
|
function verify(publicKey, signatureBytes, messageBytes) {
|
|
|
|
var split = Math.floor(signatureBytes.length / 2),
|
|
r = btd(signatureBytes.slice(0, split)),
|
|
s = btd(signatureBytes.slice(split)),
|
|
digest = getDigest(messageBytes.slice()),
|
|
u1 = [],
|
|
u2 = [];
|
|
|
|
var publicPoint = new cryptoECC.EllipticCurvePointFp(
|
|
curve, false, btd(publicKey.x), btd(publicKey.y), null, false);
|
|
|
|
cryptoMath.modInv(s, curve.order, s);
|
|
cryptoMath.modMul(digest, s, curve.order, u1);
|
|
cryptoMath.modMul(r, s, curve.order, u2);
|
|
|
|
var r0 = curve.allocatePointStorage();
|
|
var r1 = curve.allocatePointStorage();
|
|
|
|
if (tedCurve) {
|
|
cryptoMath.add(u1, u1, u1);
|
|
cryptoMath.add(u1, u1, u1);
|
|
cryptoMath.reduce(u1, curve.order, u1);
|
|
ecop.scalarMultiply(u1, curve.generator, r0, false);
|
|
ecop.scalarMultiply(u2, publicPoint, r1, false);
|
|
ecop.convertToExtendedProjective(r0);
|
|
ecop.convertToExtendedProjective(r1);
|
|
ecop.add(r1, r0, r0);
|
|
ecop.normalize(r0);
|
|
|
|
} else {
|
|
ecop.scalarMultiply(u1, curve.generator, r0);
|
|
ecop.scalarMultiply(u2, publicPoint, r1);
|
|
ecop.convertToJacobianForm(r0);
|
|
ecop.convertToMontgomeryForm(r0);
|
|
ecop.convertToMontgomeryForm(r1);
|
|
ecop.mixedAdd(r0, r1, r0);
|
|
ecop.convertToAffineForm(r0);
|
|
ecop.convertToStandardForm(r0);
|
|
}
|
|
|
|
if (r0.isInfinity) {
|
|
return false;
|
|
}
|
|
|
|
cryptoMath.reduce(r0.x, curve.order, r0.x);
|
|
|
|
return (cryptoMath.compareDigits(r0.x, r) === 0);
|
|
}
|
|
|
|
return {
|
|
createKey: createKey,
|
|
generateKey: generateKey,
|
|
sign: sign,
|
|
verify: verify
|
|
};
|
|
|
|
};
|
|
|
|
if (typeof operations !== "undefined") {
|
|
|
|
msrcryptoEcdsa.sign = function ( p) {
|
|
|
|
var hashName = p.algorithm.hash.name,
|
|
curve = cryptoECC.createCurve(p.keyHandle.algorithm.namedCurve.toUpperCase()),
|
|
hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
|
|
digest = hashFunc.computeHash(p.buffer);
|
|
|
|
var ecdsa = msrcryptoEcdsa(curve);
|
|
|
|
return ecdsa.sign(p.keyData, digest);
|
|
};
|
|
|
|
msrcryptoEcdsa.verify = function ( p) {
|
|
|
|
var hashName = p.algorithm.hash.name,
|
|
curve = cryptoECC.createCurve(p.keyHandle.algorithm.namedCurve.toUpperCase()),
|
|
hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
|
|
digest = hashFunc.computeHash(p.buffer);
|
|
|
|
var ecdsa = msrcryptoEcdsa(curve);
|
|
|
|
return ecdsa.verify(p.keyData, p.signature, digest);
|
|
};
|
|
|
|
msrcryptoEcdsa.generateKey = function (p) {
|
|
|
|
var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());
|
|
|
|
var ecdsa = msrcryptoEcdsa(curve);
|
|
|
|
var keyPairData = ecdsa.generateKey();
|
|
|
|
var dtb = cryptoMath.digitsToBytes;
|
|
|
|
// Sometimes the result is a byte short because the byte-conversion
|
|
// trims leading zeros. We pad the zeros back on if needed.
|
|
function padTo8BytesIncrement(array) {
|
|
return msrcryptoUtilities.padFront(array, 0, Math.ceil(array.length / 8) * 8);
|
|
}
|
|
var x = padTo8BytesIncrement(dtb(keyPairData.publicKey.x));
|
|
var y = padTo8BytesIncrement(dtb(keyPairData.publicKey.y));
|
|
var d = padTo8BytesIncrement(dtb(keyPairData.privateKey));
|
|
|
|
return {
|
|
type: "keyPairGeneration",
|
|
keyPair: {
|
|
publicKey: {
|
|
keyData: {
|
|
x: x,
|
|
y: y
|
|
},
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "public"
|
|
}
|
|
},
|
|
privateKey: {
|
|
keyData: {
|
|
x: x,
|
|
y: y,
|
|
d: d
|
|
},
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable,
|
|
keyUsage: null || p.keyUsage,
|
|
type: "private"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
msrcryptoEcdsa.importKey = function (p) {
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["x", "y", "d", "crv"]);
|
|
|
|
// If only private key data 'd' is imported, create x and y
|
|
if (keyObject.d && (!keyObject.x || !keyObject.y)) {
|
|
|
|
var curve = msrcryptoEcdsa.curves[p.algorithm.namedCurve]();
|
|
|
|
var ecdsa = msrcryptoEcdsa(curve);
|
|
|
|
var publicKey = ecdsa.computePublicKey(keyObject.d);
|
|
|
|
keyObject.x = publicKey.x;
|
|
keyObject.y = publicKey.y;
|
|
}
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject,
|
|
keyHandle: {
|
|
algorithm: p.algorithm,
|
|
extractable: p.extractable || keyObject.extractable,
|
|
keyUsage: null || p.keyUsage, // IE11 returns null here
|
|
type: (keyObject.d) ? "private" : "public"
|
|
}
|
|
};
|
|
};
|
|
|
|
msrcryptoEcdsa.exportKey = function (p) {
|
|
|
|
var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);
|
|
|
|
return { type: "keyExport", keyHandle: jsonKeyStringArray };
|
|
|
|
};
|
|
|
|
operations.register("sign", "ecdsa", msrcryptoEcdsa.sign);
|
|
|
|
operations.register("verify", "ecdsa", msrcryptoEcdsa.verify);
|
|
|
|
operations.register("generateKey", "ecdsa", msrcryptoEcdsa.generateKey);
|
|
|
|
operations.register("importKey", "ecdsa", msrcryptoEcdsa.importKey);
|
|
|
|
operations.register("exportKey", "ecdsa", msrcryptoEcdsa.exportKey);
|
|
|
|
}
|
|
|
|
var msrcryptoSubtle;
|
|
|
|
// This code is not used in web worker instance.
|
|
if (!runningInWorkerInstance) {
|
|
|
|
msrcryptoSubtle = (function() {
|
|
|
|
// If no native Promise support add ours
|
|
if (!window.Promise) {
|
|
|
|
window.Promise = function (executor, id) {
|
|
/// <summary>
|
|
/// Creates a new promise.
|
|
/// </summary>
|
|
/// <param name="executor" type="function">A function that takes two parameters: function(resolved, rejected){...}</param>
|
|
/// <returns type="Promise">A new Promise object</returns>
|
|
|
|
if (!(this instanceof Promise)) {
|
|
throw new Error("use 'new' keyword with Promise constructor");
|
|
}
|
|
|
|
var successResult = null,
|
|
failReason = null,
|
|
thenResolved = [],
|
|
thenRejected = [],
|
|
rejectThenPromise = [],
|
|
resolveThenPromise = [];
|
|
|
|
this.then = function (onCompleted, onRejected) {
|
|
|
|
var thenFunctionResult;
|
|
|
|
// If we already have a result because resolveFunction was synchronous,
|
|
// then just call onCompleted with the result.
|
|
if (successResult) {
|
|
thenFunctionResult = onCompleted(successResult.result);
|
|
|
|
if (thenFunctionResult && thenFunctionResult.then) {
|
|
return thenFunctionResult;
|
|
}
|
|
|
|
// Create a new promise; resolve with the result;
|
|
// return the resolved promise.
|
|
return Promise.resolve(thenFunctionResult);
|
|
}
|
|
|
|
// If we already have a fail reason from a rejected promise
|
|
if (failReason) {
|
|
thenFunctionResult = onRejected ? onRejected(failReason.result) : failReason.result;
|
|
|
|
if (thenFunctionResult && thenFunctionResult.then) {
|
|
return thenFunctionResult;
|
|
}
|
|
|
|
// Create a new promise; reject with the result;
|
|
// return the resolved promise.
|
|
return Promise.resolve(thenFunctionResult);
|
|
}
|
|
|
|
// If we do not have a result, store the onCompleted/onRejected functions
|
|
// to call when we do get a result.
|
|
thenResolved.push(onCompleted);
|
|
if (onRejected) {
|
|
thenRejected.push(onRejected);
|
|
}
|
|
|
|
// Return a new promise object. This will allow chaining with then/catch().
|
|
return new Promise(function (resolve, reject) {
|
|
resolveThenPromise.push(resolve);
|
|
rejectThenPromise.push(reject);
|
|
});
|
|
}
|
|
|
|
this["catch"] = function (onRejected) {
|
|
|
|
var catchFunctionResult;
|
|
|
|
// If we already have a result because resolveFunction was synchronous,
|
|
// then just call onRejected with the result.
|
|
if (failReason) {
|
|
catchFunctionResult = onRejected(failReason.result);
|
|
|
|
if (catchFunctionResult && catchFunctionResult.then) {
|
|
return catchFunctionResult;
|
|
}
|
|
|
|
return Promise.resolve(catchFunctionResult);
|
|
}
|
|
|
|
// If we do not have a result, store the onRejected function
|
|
// to call when we do get a result.
|
|
thenRejected.push(onRejected);
|
|
|
|
// Return a new promise object. This will allow chaining with then/catch().
|
|
return new Promise(function (resolve, reject) {
|
|
resolveThenPromise.push(resolve);
|
|
rejectThenPromise.push(reject);
|
|
});
|
|
}
|
|
|
|
var resolve = function (param) {
|
|
/// <summary>
|
|
/// Called by the executor function when the function has succeeded.
|
|
/// </summary>
|
|
/// <param name="param">A result value that will be passed to the then() function.</param>
|
|
|
|
// Call each attached Then function with the result
|
|
for (var i = 0; i < thenResolved.length; i++) {
|
|
|
|
var result = thenResolved[i](param);
|
|
|
|
// If the result of the then() function is a Promise,
|
|
// set then() to call the chained resolve function.
|
|
if (result && result.then) {
|
|
result.then(resolveThenPromise[i]);
|
|
|
|
} else {
|
|
|
|
// If a then() promise was chained to this promise, call its resolve
|
|
// function.
|
|
if (resolveThenPromise[i]) {
|
|
resolveThenPromise[i](result);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the onCompleted function has not yet been assigned, store the result.
|
|
successResult = { result: param };
|
|
|
|
return;
|
|
}
|
|
|
|
function reject(param) {
|
|
|
|
// Call each catch function on this promise
|
|
for (var i = 0; i < thenRejected.length; i++) {
|
|
|
|
var reason = thenRejected[i](param);
|
|
|
|
// If the result of the catch() function is a Promise,
|
|
// set then() to call the chained resolve function.
|
|
if (reason && reason.then) {
|
|
reason.then(resolveThenPromise[i], rejectThenPromise[i]);
|
|
|
|
} else {
|
|
if (resolveThenPromise[i]) {
|
|
resolveThenPromise[i](reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the onCompleted function has not yet been assigned, store the result.
|
|
failReason = { result: param };
|
|
|
|
return;
|
|
};
|
|
|
|
// Call the executor function passing the resolve & reject functions of
|
|
// this instance.
|
|
|
|
executor(resolve, reject);
|
|
|
|
return;
|
|
}
|
|
|
|
//#region static methods
|
|
|
|
window.Promise.all = function (promiseArray) {
|
|
/// <summary>
|
|
/// Joins two or more promises and returns only when all the specified promises have completed or been rejected.
|
|
/// </summary>
|
|
/// <param name="promiseArray" type="Array">Array of promises.</param>
|
|
/// <returns type="Promise">Returns a Promise.</returns>
|
|
|
|
var results = [],
|
|
resultCount = 0;
|
|
|
|
// Generates a then function for each promise
|
|
function then(index, resolve) {
|
|
|
|
return function (result) {
|
|
|
|
// We want the results to have the same results index as it was passed in.
|
|
results[index] = result;
|
|
|
|
// If all of the promises have returned results, call the resolve function
|
|
// with the results array.
|
|
if (++resultCount == promiseArray.length) {
|
|
resolve(results);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new Promise to return. It's resolve function will call then()
|
|
// on each promise in the arguments list.
|
|
var promiseAll = new Promise(
|
|
|
|
function (resolve, reject) {
|
|
|
|
for (var i = 0; i < promiseArray.length; i++) {
|
|
|
|
promiseArray[i].then(then(i, resolve));
|
|
|
|
// If a promise fails, return the reason
|
|
promiseArray[i]["catch"](function (reason) { reject(reason); });
|
|
}
|
|
});
|
|
|
|
return promiseAll;
|
|
};
|
|
|
|
window.Promise.race = function (promiseArray) {
|
|
/// <summary>
|
|
/// Creates a new promise that will resolve or reject with the same result value as the first promise to resolve or reject among the passed in arguments.
|
|
/// </summary>
|
|
/// <param name="promises" type="Array">Required. One or more promises.</param>
|
|
/// <returns type="Promise">Result of first promise to resolve or fail.</returns>
|
|
|
|
var resolved = false;
|
|
|
|
// Generates a then function for each promise
|
|
function then(resolveFunction) {
|
|
|
|
return function (result) {
|
|
|
|
// When the first promise succeeds/fails, return the answer and ignore the rest.
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolveFunction(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new Promise to return. It's resolve function will call then()
|
|
// on each promise in the arguments list.
|
|
var promiseRace = new Promise(
|
|
|
|
function (resolve, reject) {
|
|
|
|
for (var i = 0; i < promiseArray.length; i++) {
|
|
promiseArray[i].then(then(resolve), then(reject));
|
|
}
|
|
});
|
|
|
|
return promiseRace;
|
|
};
|
|
|
|
window.Promise.reject = function (rejectReason) {
|
|
/// <summary>
|
|
/// Creates a new rejected promise with a result equal to the passed in argument.
|
|
/// </summary>
|
|
/// <param name="rejectReason" type="">Required. The reason why the promise was rejected.</param>
|
|
/// <returns type=""></returns>
|
|
|
|
return new Promise(
|
|
function (resolve, reject) {
|
|
reject(rejectReason);
|
|
});
|
|
};
|
|
|
|
window.Promise.resolve = function (resolveResult) {
|
|
/// <summary>
|
|
/// Creates a new resolved promise with a result equal to its argument.
|
|
/// </summary>
|
|
/// <param name="resolveResult" type="">Required. The value returned with the completed promise.</param>
|
|
/// <returns type=""></returns>
|
|
|
|
return new Promise(
|
|
function (resolve, reject) {
|
|
resolve(resolveResult);
|
|
});
|
|
};
|
|
|
|
//#endregion static methods
|
|
}
|
|
|
|
// This worker is used when webworkers aren't available.
|
|
// It will function synchronously but use the same
|
|
// mechanisms that the asynchronous webworkers use.
|
|
function syncWorker() {
|
|
var result;
|
|
|
|
// PostMessage is how you interact with a worker. You post some data to the worker
|
|
// and it will process it and return it's data to the onmessage function.
|
|
// Since we're really running synchronously, we call the crypto function in
|
|
// PostMessage and wait for the result. Then we call the OnMessage fuction with
|
|
// that result. This will give the same behavior as a web-worker.
|
|
function postMessage(data) {
|
|
|
|
// Web-workers will automatically return an error message when an
|
|
// error is thrown within the web worker.
|
|
// When using a sync worker, we'll have to catch thrown errors, so we
|
|
// need a try/catch block here.
|
|
try {
|
|
result = msrcryptoWorker.jsCryptoRunner( { data: data });
|
|
} catch (ex) {
|
|
this.onerror({ data: ex.message, type: "error" });
|
|
return;
|
|
}
|
|
|
|
// 'process' operations don't return values, so we don't
|
|
// forward the worker return message.
|
|
if (!data.operationSubType || data.operationSubType !== "process") {
|
|
this.onmessage({ data: result });
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
postMessage: postMessage,
|
|
onmessage: null,
|
|
onerror: null,
|
|
terminate: function () {
|
|
// This is a no-op to be compatible with webworker.
|
|
}
|
|
};
|
|
}
|
|
|
|
/// <dictionary>Obj,oncomplete,onerror</dictionary>
|
|
|
|
function baseOperation(processResults) {
|
|
|
|
var result = null,
|
|
oncompleteCallback = null,
|
|
onerrorCallback = null,
|
|
retObj,
|
|
promise,
|
|
resolveFunc,
|
|
rejectFunc;
|
|
|
|
// Create a new promise
|
|
promise = new Promise(
|
|
function (resolve, reject) {
|
|
resolveFunc = resolve;
|
|
rejectFunc = reject;
|
|
});
|
|
|
|
// Called when the worker returns a result
|
|
function opDispatchEvent( e) {
|
|
|
|
// If the event is an Error call the onError callback
|
|
if (e.type === "error") {
|
|
|
|
// If the onerror callback has been set, call it.
|
|
// If the onerror callback has been set, call it.
|
|
if (rejectFunc) {
|
|
rejectFunc.apply(promise, [e.message ? e.message : e]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Otherwise call the oncomplete callback
|
|
this.result = processResults(e.data);
|
|
|
|
// Resolve the promise with the result
|
|
resolveFunc.apply(promise, [this.result]);
|
|
|
|
return;
|
|
}
|
|
|
|
retObj = {
|
|
dispatchEvent: opDispatchEvent,
|
|
promise : promise,
|
|
result: null
|
|
};
|
|
|
|
return retObj;
|
|
}
|
|
|
|
function keyOperation() {
|
|
|
|
function processResult(result) {
|
|
|
|
// Could be the result of an import, export, generate.
|
|
// Get the keyData and keyHandle out.
|
|
switch (result.type) {
|
|
|
|
// KeyImport: save the new key
|
|
case "keyGeneration":
|
|
case "keyImport":
|
|
case "keyDerive":
|
|
keys.add(result.keyHandle, result.keyData);
|
|
|
|
return result.keyHandle;
|
|
|
|
// KeyExport: return the export data
|
|
case "keyExport":
|
|
return result.keyHandle;
|
|
|
|
case "keyPairGeneration":
|
|
keys.add(result.keyPair.publicKey.keyHandle, result.keyPair.publicKey.keyData);
|
|
keys.add(result.keyPair.privateKey.keyHandle, result.keyPair.privateKey.keyData);
|
|
return {
|
|
publicKey: result.keyPair.publicKey.keyHandle,
|
|
privateKey: result.keyPair.privateKey.keyHandle
|
|
};
|
|
|
|
default:
|
|
throw new Error("Unknown key operation");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
return baseOperation(processResult);
|
|
}
|
|
|
|
function cryptoOperation(cryptoContext) {
|
|
|
|
function processResult(result) {
|
|
|
|
// If the browser supports typed-arrays, return an ArrayBuffer like IE11.
|
|
result = toArrayBufferIfSupported(result);
|
|
|
|
// A normal array will be returned.
|
|
return result;
|
|
}
|
|
|
|
var op = baseOperation(processResult);
|
|
|
|
op.process = function (buffer) {
|
|
cryptoContext.operationSubType = "process";
|
|
cryptoContext.buffer = utils.toArray(buffer);
|
|
workerManager.continueJob(this,
|
|
utils.clone(cryptoContext));
|
|
};
|
|
|
|
op.finish = function () {
|
|
cryptoContext.operationSubType = "finish";
|
|
cryptoContext.buffer = [];
|
|
workerManager.continueJob(this,
|
|
utils.clone(cryptoContext));
|
|
};
|
|
|
|
op.abort = function () {
|
|
workerManager.abortJob(this);
|
|
};
|
|
|
|
op.onabort = null;
|
|
op.onprogress = null;
|
|
|
|
op.algorithm = cryptoContext.algorithm || null;
|
|
op.key = cryptoContext.keyHandle || null;
|
|
|
|
return op;
|
|
}
|
|
|
|
function toArrayBufferIfSupported(dataArray) {
|
|
|
|
// If the browser supports typed-arrays, return an ArrayBuffer like IE11.
|
|
if (typedArraySupport && dataArray.pop) {
|
|
|
|
// We can't write to an ArrayBuffer directly so we create a Uint8Array
|
|
// and return it's buffer property.
|
|
return (new Uint8Array(dataArray)).buffer;
|
|
}
|
|
|
|
// Do nothing and just return the passed-in array.
|
|
return dataArray;
|
|
}
|
|
|
|
// Storage for the keyData.
|
|
// Stored as {keyHandle: keyHandle, keyData: keyData} objects.
|
|
var keys = [];
|
|
keys.add = function (keyHandle, keyData) {
|
|
keys.push({ keyHandle: keyHandle, keyData: keyData });
|
|
};
|
|
keys.remove = function (keyHandle) {
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if (keys[i].keyHandle === keyHandle) {
|
|
keys = keys.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
keys.lookup = function (keyHandle) {
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if (keys[i].keyHandle === keyHandle) {
|
|
return keys[i].keyData;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Manages the pool of webworkers and job queue.
|
|
// We first try to find an idle webworker and pass it a crypto job.
|
|
// If there are no workers or they are all busy, we'll create a new one.
|
|
// If we're at our (somewhat arbitrary) limit for workers we'll queue the
|
|
// job until a worker is free.
|
|
// When a worker finishes and the queue is empty it will kill itself to
|
|
// free resources.
|
|
// However, we will keep a couple idle workers alive for future use.
|
|
// In the case webworkers are not supported <IE10 we will run in synchronous
|
|
// mode. Jobs will be executed synchronously as they arrive using a single
|
|
// syncWorker (pretend webworker that just runs synchronously in this same script).
|
|
var workerManager = (function () {
|
|
|
|
// The max number of webworkers we'll spawn.
|
|
var maxWorkers = 12;
|
|
|
|
// The number of idle webworkers we'll allow to live for future use.
|
|
var maxFreeWorkers = 2;
|
|
|
|
// Storage for webworker.
|
|
var workerPool = [];
|
|
|
|
// Queue for jobs when all workers are busy.
|
|
var jobQueue = [];
|
|
|
|
// Each job gets and id.
|
|
var jobId = 0;
|
|
|
|
function getFreeWorker() {
|
|
|
|
purgeWorkerType(!asyncMode);
|
|
|
|
// Get the first non-busy worker
|
|
for (var i = 0; i < workerPool.length; i++) {
|
|
if (!workerPool[i].busy) {
|
|
return workerPool[i];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function purgeWorkerType(webWorker) {
|
|
for (var i = workerPool.length - 1; i >= 0; i -= 1) {
|
|
if (workerPool[i].isWebWorker === webWorker) {
|
|
workerPool[i].terminate();
|
|
workerPool.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function freeWorkerCount() {
|
|
var freeWorkers = 0;
|
|
for (var i = 0; i < workerPool.length; i++) {
|
|
if (!workerPool[i].busy) {
|
|
freeWorkers += 1;
|
|
}
|
|
}
|
|
return freeWorkers;
|
|
}
|
|
|
|
function addWorkerToPool(worker) {
|
|
workerPool.push(worker);
|
|
}
|
|
|
|
function removeWorkerFromPool(worker) {
|
|
// Find this worker in the array.
|
|
for (var i = 0; i < workerPool.length; i++) {
|
|
if (workerPool[i] === worker) {
|
|
// Kill the webworker.
|
|
worker.terminate();
|
|
// Remove the worker object from the pool.
|
|
workerPool.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function lookupWorkerByOperation(operation) {
|
|
// Find this worker in the array.
|
|
for (var i = 0; i < workerPool.length; i++) {
|
|
if (workerPool[i].operation === operation) {
|
|
return workerPool[i];
|
|
}
|
|
}
|
|
// Didn't find the worker!?
|
|
return null;
|
|
}
|
|
|
|
function queueJob(operation, data) {
|
|
jobQueue.push({ operation: operation, data: data, id: jobId++ });
|
|
}
|
|
|
|
function jobCompleted(worker) {
|
|
|
|
worker.busy = false;
|
|
worker.operation = null;
|
|
|
|
// Check the queue for waiting jobs if in async mode
|
|
if (asyncMode) {
|
|
if (jobQueue.length > 0) {
|
|
var job = jobQueue.shift();
|
|
continueJob(job.operation, job.data);
|
|
|
|
} else if (freeWorkerCount() > maxFreeWorkers) {
|
|
removeWorkerFromPool(worker);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
function createNewWorker(operation) {
|
|
|
|
// Use a web worker if supported
|
|
// else use a synchronous worker.
|
|
var worker;
|
|
|
|
if (asyncMode) {
|
|
try {
|
|
worker = new Worker(scriptUrl);
|
|
worker.postMessage({ prngSeed: msrcryptoPseudoRandom.getBytes(48) });
|
|
worker.isWebWorker = true;
|
|
} catch (ex) {
|
|
asyncMode = false;
|
|
publicMethods.forceSync = true;
|
|
worker = syncWorker();
|
|
worker.isWebWorker = false;
|
|
}
|
|
|
|
} else {
|
|
worker = syncWorker();
|
|
worker.isWebWorker = false;
|
|
}
|
|
|
|
// Store the operation object as a property on the worker
|
|
// so we can know which operation this worker is working for.
|
|
worker.operation = operation;
|
|
|
|
worker.busy = false;
|
|
|
|
// The worker will call this function when it completes its job.
|
|
worker.onmessage = function ( e) {
|
|
|
|
var op = worker.operation;
|
|
|
|
// Check if there are queued jobs for this operation
|
|
for (var i = 0; i < jobQueue.length; i++) {
|
|
if (jobQueue[i].operation === worker.operation) {
|
|
var job = jobQueue[i];
|
|
jobQueue.splice(i, 1);
|
|
postMessageToWorker(worker, job.data);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Send the results to the operation object and it will fire
|
|
// it's onCompleted event.
|
|
if (op && e.data.type !== "process") {
|
|
jobCompleted(worker);
|
|
op.dispatchEvent(e);
|
|
}
|
|
};
|
|
|
|
// If an error occurs within the worker.
|
|
worker.onerror = function ( e) {
|
|
|
|
var op = worker.operation;
|
|
|
|
jobCompleted(worker);
|
|
|
|
// Send the error to the operation object and it will fire
|
|
// it's onError event.
|
|
op.dispatchEvent(e);
|
|
|
|
};
|
|
|
|
// Add this new worker to the worker pool.
|
|
addWorkerToPool(worker);
|
|
|
|
return worker;
|
|
}
|
|
|
|
function abortJob(cryptoOperationObject) {
|
|
var worker = lookupWorkerByOperation(cryptoOperationObject);
|
|
if (worker) {
|
|
removeWorkerFromPool(worker);
|
|
}
|
|
}
|
|
|
|
// Creates or reuses a worker and starts it up on work.
|
|
function runJob( operation, data) {
|
|
|
|
var worker = null;
|
|
|
|
// If the caller adds the "forceSync" property and sets it to true.
|
|
// Then run in synchronous mode even if webworkers are available.
|
|
// This can be turned on or off on the fly.
|
|
asyncMode = webWorkerSupport && !(publicMethods.forceSync);
|
|
|
|
// Get the first idle worker.
|
|
worker = getFreeWorker();
|
|
|
|
// Queue this job if all workers are busy and we're at our max instances
|
|
if (asyncMode && worker === null && workerPool.length >= maxWorkers) {
|
|
queueJob(operation, data);
|
|
return;
|
|
}
|
|
|
|
// No idle workers, we'll have to create a new one.
|
|
if (worker === null) {
|
|
worker = createNewWorker(operation);
|
|
}
|
|
|
|
if (worker === null) {
|
|
queueJob(operation, data);
|
|
throw new Error("could not create new worker");
|
|
}
|
|
|
|
// Store the operation object as a property on the worker
|
|
// so we can know which operation this worker is working for.
|
|
worker.operation = operation;
|
|
|
|
// Mark this worker as 'busy'. It's about to run a job.
|
|
worker.busy = true;
|
|
|
|
// Start the worker
|
|
postMessageToWorker(worker, data);
|
|
|
|
}
|
|
|
|
function continueJob(/*type(cryptoOperation)*/operation, data) {
|
|
|
|
// Lookup the worker that is handling this operation
|
|
var worker = lookupWorkerByOperation(operation);
|
|
|
|
if (worker) {
|
|
postMessageToWorker(worker, data);
|
|
return;
|
|
}
|
|
|
|
// If we didn't find a worker, this is probably the first
|
|
// 'process' message so we need to start a new worker.
|
|
runJob(operation, data);
|
|
|
|
}
|
|
|
|
function postMessageToWorker(worker, data) {
|
|
// Start the worker now if using webWorkers
|
|
// else, defer running until later.
|
|
if (asyncMode) {
|
|
worker.data = data;
|
|
worker.postMessage(data);
|
|
} else {
|
|
|
|
var func = (function (postData) {
|
|
|
|
return function () {
|
|
return worker.postMessage(postData);
|
|
}
|
|
|
|
})(data);
|
|
|
|
var id = setTimeout(func, 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
return {
|
|
runJob: runJob,
|
|
continueJob: continueJob,
|
|
abortJob: abortJob
|
|
};
|
|
|
|
})();
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
function checkOperation(operationType, algorithmName) {
|
|
if (!operations.exists(operationType, algorithmName)) {
|
|
throw new Error("unsupported algorithm");
|
|
}
|
|
}
|
|
|
|
// The list of possible parameters passed to the subtle interface.
|
|
var subtleParameters = [
|
|
/* 0 */ { name: "algorithm", type: "Object", required: true },
|
|
/* 1 */ { name: "keyHandle", type: "Object", required: true },
|
|
/* 2 */ { name: "buffer", type: "Array", required: false },
|
|
/* 3 */ { name: "signature", type: "Array", required: true },
|
|
/* 4 */ { name: "format", type: "String", required: true },
|
|
/* 5 */ { name: "keyData", type: "Object", required: true },
|
|
/* 6 */ { name: "extractable", type: "Boolean", required: false },
|
|
/* 7 */ { name: "keyUsage", type: "Array", required: false },
|
|
/* 8 */ { name: "derivedKeyType", type: "Object", required: true },
|
|
/* 9 */ { name: "length", type: "Number", required: false },
|
|
|
|
/* 10 */ { name: "extractable", type: "Boolean", required: true },
|
|
/* 11 */ { name: "keyUsage", type: "Array", required: true }
|
|
];
|
|
|
|
// The set of expected parameters passed to each subtle function.
|
|
var subtleParametersSets = {
|
|
encrypt: [0, 1, 2],
|
|
decrypt: [0, 1, 2],
|
|
sign: [0, 1, 2],
|
|
verify: [0, 1, 3, 2],
|
|
digest: [0, 2],
|
|
generateKey: [0, 6, 7],
|
|
importKey: [4, 5, 0, 10, 11],
|
|
exportKey: [0, 4, 1, 6, 7],
|
|
deriveKey: [0, 1, 8, 6, 7],
|
|
deriveBits: [0, 1, 9],
|
|
wrapKey: [1, 1, 0],
|
|
unwrapKey: [2, 0, 1, 6, 7]
|
|
};
|
|
|
|
// Looks up the stored key data for a given keyHandle
|
|
function lookupKeyData(handle) {
|
|
var data = keys.lookup(handle);
|
|
|
|
if (!data) {
|
|
throw new Error("key not found");
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// This function processes each parameter passed by the user. Each parameter
|
|
// is compared against an expected parameter. It should be of the expected type.
|
|
// Typed-Array parameters are converted to regular Arrays.
|
|
function buildParameterCollection(operationName, parameterSet) {
|
|
|
|
var parameterCollection = { operationType: operationName },
|
|
operationParameterSet = subtleParametersSets[operationName];
|
|
|
|
for (var i = 0; i < operationParameterSet.length; i += 1) {
|
|
|
|
var expectedParam = subtleParameters[operationParameterSet[i]];
|
|
var actualParam = parameterSet[i];
|
|
|
|
// Verify the required parameters are present.
|
|
if (actualParam==null) {
|
|
if (expectedParam.required) {
|
|
throw new Error(expectedParam.name);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If this parameter is a typed-array convert it to a regular array.
|
|
if (actualParam.subarray) {
|
|
actualParam = utils.toArray(actualParam);
|
|
}
|
|
|
|
// If this parameter is an ArrayBuffer convert it to a regular array.
|
|
if (utils.getObjectType(actualParam) == "ArrayBuffer") {
|
|
actualParam = utils.toArray(actualParam);
|
|
}
|
|
|
|
// Verify the actual parameter is of the expected type.
|
|
if (msrcryptoUtilities.getObjectType(actualParam) !== expectedParam.type) {
|
|
throw new Error(expectedParam.name);
|
|
}
|
|
|
|
// If this parameter is an algorithm object convert it's name to lowercase.
|
|
if (expectedParam.name === "algorithm") {
|
|
|
|
actualParam.name = actualParam.name.toLowerCase();
|
|
|
|
// If the algorithm has a typed-array IV, convert it to a regular array.
|
|
if (actualParam.iv) {
|
|
actualParam.iv = utils.toArray(actualParam.iv);
|
|
}
|
|
|
|
// If the algorithm has a typed-array Salt, convert it to a regular array.
|
|
if (actualParam.salt) {
|
|
actualParam.salt = utils.toArray(actualParam.salt);
|
|
}
|
|
|
|
// If the algorithm has a typed-array AdditionalData, convert it to a regular array.
|
|
if (actualParam.additionalData) {
|
|
actualParam.additionalData = utils.toArray(actualParam.additionalData);
|
|
}
|
|
|
|
// If this algorithm has a hash property in the form 'hash: hashName'
|
|
// Convert it to hash: {name: hashName} as per the W3C spec.
|
|
if (actualParam.hash && !actualParam.hash.name && msrcryptoUtilities.getObjectType(actualParam.hash) === "String") {
|
|
actualParam.hash = { name: actualParam.hash };
|
|
}
|
|
}
|
|
|
|
// KeyWrap has two keyHandle paramters. We add '1' to the second param name
|
|
// to avoid a duplicate name.
|
|
if (parameterCollection.hasOwnProperty(expectedParam.name)) {
|
|
parameterCollection[expectedParam.name + "1"] = actualParam;
|
|
} else {
|
|
parameterCollection[expectedParam.name] = actualParam;
|
|
}
|
|
}
|
|
|
|
return parameterCollection;
|
|
}
|
|
|
|
function executeOperation(operationName, parameterSet, keyFunc) {
|
|
|
|
var pc = buildParameterCollection(operationName, parameterSet);
|
|
|
|
// Verify this type of operation is supported by this library (encrypt, digest, etc...)
|
|
checkOperation(operationName, pc.algorithm.name);
|
|
|
|
// Add the key data to the parameter object
|
|
if (pc.keyHandle) {
|
|
pc.keyData = lookupKeyData(pc.keyHandle);
|
|
}
|
|
|
|
// Add the key data to the parameter object
|
|
// KeyWrap has two keyHandle parameters - this handles the second key.
|
|
if (pc.keyHandle1) {
|
|
pc.keyData1 = lookupKeyData(pc.keyHandle1);
|
|
}
|
|
|
|
// ECDH.DeriveBits passes a public key in the algorithm
|
|
if (pc.algorithm && pc.algorithm.public) {
|
|
pc.additionalKeyData = lookupKeyData(pc.algorithm.public);
|
|
}
|
|
|
|
var op = keyFunc ? keyOperation(pc) : cryptoOperation(pc);
|
|
|
|
// Run the crypto now if a buffer is supplied
|
|
// else wait until process() and finish() are called.
|
|
if (keyFunc || pc.buffer || operationName === "deriveBits" || operationName === "wrapKey") {
|
|
workerManager.runJob(op, pc);
|
|
}
|
|
|
|
return op.promise;
|
|
}
|
|
|
|
var publicMethods = {
|
|
|
|
encrypt: function (algorithm, keyHandle, buffer) {
|
|
/// <signature>
|
|
/// <summary>Encrypt a UInt8Array of data. Encrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
|
|
/// <returns type="ArrayBuffer" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Encrypt an array of bytes. Encrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="Array" optional="true">An array of bytes (number from 0-255)</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Encrypt an array of bytes. Encrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="Array" optional="true">A continuous array of bytes (number values from 0-255)</param>
|
|
/// <returns type="ArrayBuffer" />
|
|
/// </signature>
|
|
|
|
return executeOperation("encrypt", arguments, 0);
|
|
},
|
|
|
|
decrypt: function (algorithm, keyHandle, buffer) {
|
|
/// <signature>
|
|
/// <summary>Decrypt a UInt8Array of data.
|
|
/// Decrypt will return an ArrayBuffer if supported, otherwise it will return an Array of byte values (numbers from 0-255)</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Decrypt an array of byte values. Decrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("decrypt", arguments, 0);
|
|
},
|
|
|
|
sign: function (algorithm, keyHandle, buffer) {
|
|
/// <signature>
|
|
/// <summary>Sign a UInt8Array of data.
|
|
/// Sign will return a signature as an ArrayBuffer if supported, otherwise it will return an Array of byte values (numbers from 0-255)</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Sign an array of byte values. Sign will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("sign", arguments, 0);
|
|
},
|
|
|
|
verify: function (algorithm, keyHandle, signature, buffer) {
|
|
/// <signature>
|
|
/// <summary>Verify a signature.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="signature" type="UInt8Array">UInt8Array</param>
|
|
/// <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Verify a signature.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="signature" type="UInt8Array">UInt8Array</param>
|
|
/// <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Verify a signature.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="signature" type="Array">An array of bytes values (numbers from 0-255)</param>
|
|
/// <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Verify a signature.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="signature" type="Array">An array of bytes values (numbers from 0-255)</param>
|
|
/// <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("verify", arguments, 0);
|
|
},
|
|
|
|
digest: function (algorithm, buffer) {
|
|
/// <signature>
|
|
/// <summary>Digest data using a specified cryptographic hash algorithm</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Digest data using a specified cryptographic hash algorithm</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
return executeOperation("digest", arguments, 0);
|
|
},
|
|
|
|
generateKey: function (algorithm, extractable, keyUsage) {
|
|
/// <signature>
|
|
/// <summary>Generate a new key for use with the algorithm specified by the algorithm parameter</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="extractable" type="Boolean" optional="true"></param>
|
|
/// <param name="keyUsage" type="Array" optional="true"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("generateKey", arguments, 1);
|
|
},
|
|
|
|
deriveKey: function (algorithm, baseKey, derivedKeyType, extractable, keyUsage) {
|
|
/// <signature>
|
|
/// <summary>Generate a key for the specified derivedKeyType, using the specified cryptographic key derivation algorithm with the given baseKey as input.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="baseKey" type="Key"></param>
|
|
/// <param name="deriveKeyType" type="Algorithm"></param>
|
|
/// <param name="extractable" type="Boolean" optional="true"></param>
|
|
/// <param name="keyUsage" type="Array" optional="true"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("deriveKey", arguments, 1);
|
|
},
|
|
|
|
deriveBits: function (algorithm, baseKey, length) {
|
|
/// <signature>
|
|
/// <summary>Generate an array of bytes from a given baseKey as input.</summary>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="baseKey" type="Key"></param>
|
|
/// <param name="length" type="Number">Number of bytes to return.</param>
|
|
/// <returns type="CryptoOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("deriveBits", arguments, 0);
|
|
},
|
|
|
|
importKey: function (format, keyData, algorithm, extractable, keyUsage) {
|
|
/// <signature>
|
|
/// <summary>Constructs a new Key object using the key data specified by the keyData parameter.</summary>
|
|
/// <param name="format" type="String"></param>
|
|
/// <param name="keyData" type="Object">An object representing a key in jwk format.</param>
|
|
/// <param name="algorithm" type="Algorithm"></param>
|
|
/// <param name="extractable" type="Boolean" optional="true"></param>
|
|
/// <param name="keyUsage" type="Array" optional="true"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("importKey", arguments, 1);
|
|
},
|
|
|
|
exportKey: function (format, keyHandle) {
|
|
/// <signature>
|
|
/// <summary>Exports the given key material of the Key object as specified by the key parameter.</summary>
|
|
/// <param name="format" type="String"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
|
|
// Export is one of the few calls where the caller does not supply an algorithm
|
|
// since it's already a property of the key to be exported.
|
|
// So, we're pulling it out of the key and adding it to the parameter set since
|
|
// it is used as a switch to route the parameters to the right function.
|
|
// Now we don't have to treat this as a special case in the underlying code.
|
|
return executeOperation("exportKey", [keyHandle.algorithm, format, keyHandle], 1);
|
|
},
|
|
|
|
wrapKey: function (format, key, wrappingKey, wrappingKeyAlgorithm) {
|
|
/// <signature>
|
|
/// <summary>Returns a KeyOperation object which will asynchronously return an array containing the key material of key, encrypted with keyEncryptionKey using the specified keyWrappingAlgorithm.</summary>
|
|
/// <param name="format" type="String"></param>
|
|
/// <param name="key" type="Key"></param>
|
|
/// <param name="wrappingKey" type="Key"></param>
|
|
/// <param name="wrappingKeyAlgorithm" type="Algorithm"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("wrapKey", arguments, 0);
|
|
},
|
|
|
|
unwrapKey: function (format, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsage) {
|
|
/// <signature>
|
|
/// <summary>Construct a Key object from encrypted key material.</summary>
|
|
/// <param name="format" type="String"></param>
|
|
/// <param name="unwrappingKey" type="Array">An array of bytes values (numbers from 0-255)</param>
|
|
/// <param name="unwrapAlgorithm" type="Algorithm"></param>
|
|
/// <param name="keyEncryptionKey" type="Key"></param>
|
|
/// <param name="extractableunwrappedKeyAlgorithm type="Boolean" optional="true"></param>
|
|
/// <param name="keyUsage" type="Array" optional="true"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Construct a Key object from encrypted key material.</summary>
|
|
/// <param name="format" type="String"></param>
|
|
/// <param name="unwrappingKey" type="UInt8Array"></param>
|
|
/// <param name="unwrapAlgorithm" type="Algorithm"></param>
|
|
/// <param name="unwrappedKeyAlgorithm" type="Key"></param>
|
|
/// <param name="extractable" type="Boolean" optional="true"></param>
|
|
/// <param name="keyUsage" type="Array" optional="true"></param>
|
|
/// <returns type="KeyOperation" />
|
|
/// </signature>
|
|
|
|
return executeOperation("unwrapKey", arguments, 1);
|
|
}
|
|
|
|
};
|
|
|
|
return publicMethods;
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
var msrcryptoWrapKey = (function () {
|
|
|
|
var utils = msrcryptoUtilities;
|
|
|
|
function wrapKey(params) {
|
|
|
|
var rsaObj = msrcryptoRsa(
|
|
params.keyData1,
|
|
params.keyHandle1.algorithm.name,
|
|
msrcryptoHashFunctions["sha-1"]);
|
|
|
|
var tagLength = 128;
|
|
|
|
var keyToWrapJwk = msrcryptoJwk.keyToJwkOld(params.keyHandle, params.keyData);
|
|
|
|
var jweHeader = {
|
|
"alg": params.keyHandle1.algorithm.name.toUpperCase(),
|
|
"enc": "A128GCM"
|
|
};
|
|
|
|
var encodedJweHeader =
|
|
utils.toBase64(JSON.stringify(jweHeader), true);
|
|
|
|
var cmk = msrcryptoPseudoRandom.getBytes(32);
|
|
|
|
var jweEncryptedKey = rsaObj.encrypt(cmk);
|
|
|
|
var encodedJweEncryptedKey = utils.toBase64(jweEncryptedKey, true);
|
|
|
|
var jweIv = msrcryptoPseudoRandom.getBytes(12);
|
|
|
|
var encodedJweIv = utils.toBase64(jweIv, true);
|
|
|
|
var additionalData = encodedJweHeader.concat(".", encodedJweEncryptedKey, ".", encodedJweIv);
|
|
|
|
var gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(cmk));
|
|
gcm.init(jweIv, utils.stringToBytes(additionalData), tagLength);
|
|
|
|
var ciphertextPlusTag = gcm.encrypt(keyToWrapJwk);
|
|
|
|
var tag = ciphertextPlusTag.slice(-(tagLength / 8));
|
|
|
|
var encodedIntegrityValue = utils.toBase64(tag, true);
|
|
|
|
var encodedCiphertext =
|
|
utils.toBase64(ciphertextPlusTag.slice(0, ciphertextPlusTag.length - tag.length), true);
|
|
|
|
var jwe = {
|
|
|
|
recipients: [{
|
|
header: encodedJweHeader,
|
|
encrypted_key: encodedJweEncryptedKey,
|
|
integrity_value: encodedIntegrityValue
|
|
}
|
|
],
|
|
initialization_vector: encodedJweIv,
|
|
ciphertext: encodedCiphertext
|
|
|
|
}
|
|
|
|
return utils.stringToBytes(JSON.stringify(jwe));
|
|
|
|
}
|
|
|
|
function unwrapKey(params) {
|
|
|
|
var b64Tobytes = utils.base64ToBytes;
|
|
|
|
var keyDataJwk =
|
|
JSON.parse(String.fromCharCode.apply(null, params.buffer));
|
|
|
|
var header = utils.base64ToString(keyDataJwk.recipients[0].header);
|
|
|
|
var encrypted_key =
|
|
b64Tobytes(keyDataJwk.recipients[0].encrypted_key);
|
|
|
|
var integrity_value =
|
|
b64Tobytes(keyDataJwk.recipients[0].integrity_value);
|
|
|
|
var initialization_vector =
|
|
b64Tobytes(keyDataJwk.initialization_vector);
|
|
|
|
var ciphertext =
|
|
b64Tobytes(keyDataJwk.ciphertext);
|
|
|
|
var hashFunc = msrcryptoHashFunctions["sha-1"];
|
|
var rsaObj = msrcryptoRsa(params.keyData, params.keyHandle.algorithm.name, hashFunc);
|
|
var inKey = rsaObj.decrypt(encrypted_key);
|
|
|
|
var additionalData =
|
|
keyDataJwk.recipients[0].header.concat(".", keyDataJwk.recipients[0].encrypted_key, ".", keyDataJwk.initialization_vector);
|
|
|
|
var gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(inKey));
|
|
gcm.init(initialization_vector, utils.stringToBytes(additionalData), 128);
|
|
|
|
var result = gcm.decrypt(ciphertext, integrity_value);
|
|
|
|
var keyObject = msrcryptoJwk.jwkToKey(result, params.algorithm, ["k"]);
|
|
|
|
return {
|
|
type: "keyImport",
|
|
keyData: keyObject.k,
|
|
keyHandle: {
|
|
algorithm: { name: params.algorithm.name },
|
|
extractable: params.extractable || keyObject.extractable,
|
|
keyUsage: params.keyUsage,
|
|
type: "secret"
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
wrapKey: wrapKey,
|
|
unwrapKey: unwrapKey
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
if (typeof operations !== "undefined") {
|
|
operations.register("wrapKey", "aes-gcm", msrcryptoWrapKey.wrapKey);
|
|
operations.register("unwrapKey", "aes-cbc", msrcryptoWrapKey.unwrapKey);
|
|
}
|
|
|
|
var publicMethods = {
|
|
|
|
/// <field type = 'Object' static="false">Microsoft Research Javascript Crypto Library Subtle interface.</field>
|
|
subtle: msrcryptoSubtle,
|
|
|
|
getRandomValues: function(array) {
|
|
/// <signature>
|
|
/// <summary>Places cryptographically random values into the given array.</summary>
|
|
/// <param name="array" type="Array"></param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Places cryptographically random values into the given array.</summary>
|
|
/// <param name="array" type="ArrayBufferView"></param>
|
|
/// <returns type="ArrayBufferView">Returns ArrayBufferView if supported.</returns>
|
|
/// </signature>
|
|
|
|
var i;
|
|
var randomValues = msrcryptoPseudoRandom.getBytes(array.length);
|
|
for (i = 0; i < array.length; i+=1) {
|
|
array[i] = randomValues[i];
|
|
}
|
|
return array;
|
|
},
|
|
|
|
initPrng: function (entropyData) {
|
|
/// <signature>
|
|
/// <summary>Add entropy to the PRNG.</summary>
|
|
/// <param name="entropyData" type="Array">Entropy input to seed or reseed the PRNG.</param>
|
|
/// </signature>
|
|
|
|
var entropyDataType = Object.prototype.toString.call(entropyData);
|
|
|
|
if (entropyDataType !== "[object Array]" && entropyDataType !== "[object Uint8Array]") {
|
|
throw new Error("entropyData must be a Array or Uint8Array");
|
|
}
|
|
|
|
// Mix the user-provided entropy into the entropy pool - only in the main thread.
|
|
entropyPool && entropyPool.reseed(entropyData);
|
|
|
|
// Reseed the PRNG that was initialized below
|
|
msrcryptoPseudoRandom.reseed(entropyPool.read(48));
|
|
fprngEntropyProvided = true;
|
|
},
|
|
|
|
toBase64: function (data, toBase64Url) {
|
|
/// <signature>
|
|
/// <summary>Convert string or array data to Base64.</summary>
|
|
/// <param name="data" type="Array">Byte values (numbers 0-255)</param>
|
|
/// <param name="toBase64Url" type="Boolean" optional="true">Return Base64Url encoding (this is different from Base64 encoding.)</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
/// <signature>
|
|
/// <summary>Convert string or array data to Base64.</summary>
|
|
/// <param name="data" type="String"></param>
|
|
/// <param name="toBase64Url" type="Boolean" optional="true">Return Base64Url encoding (this is different from Base64 encoding.)</param>
|
|
/// <returns type="Array" />
|
|
/// </signature>
|
|
|
|
return msrcryptoUtilities.toBase64(data, false);
|
|
},
|
|
|
|
base64ToString: function (base64String) {
|
|
/// <signature>
|
|
/// <summary>Decode a Base64 encoded string to a plain string.</summary>
|
|
/// <param name="base64String" type="String">Base64 encoded string.</param>
|
|
/// <returns type="String" />
|
|
/// </signature>
|
|
return msrcryptoUtilities.base64ToString(base64String);
|
|
},
|
|
|
|
/// <field type = 'String'>URL of the this msrCrypto script.</field>
|
|
url : scriptUrl
|
|
|
|
};
|
|
|
|
// Expose the math library if present
|
|
if (typeof cryptoMath !== "undefined") {
|
|
publicMethods.cryptoMath = cryptoMath;
|
|
}
|
|
|
|
if (typeof testInterface !== "undefined") {
|
|
publicMethods.testInterface = testInterface;
|
|
}
|
|
|
|
// Initialize the main entropy pool instance on the main thread, only.
|
|
// I want only the main thread to create and manage the central entropy pool.
|
|
// All workers would have their own PRNG instance initialized by injected entropy from the main thread.
|
|
var entropyPool;
|
|
if (!runningInWorkerInstance) {
|
|
entropyPool = entropyPool || new MsrcryptoEntropy();
|
|
|
|
// Initialize the entropy pool in the main thread.
|
|
// There is only one entropy pool.
|
|
entropyPool.init();
|
|
var localEntropy = entropyPool.read(48); // 48 is from SP800-90A; could be longer
|
|
msrcryptoPseudoRandom.init(localEntropy);
|
|
}
|
|
|
|
return publicMethods;
|
|
|
|
})();
|