mirror of
https://github.com/Ylianst/MeshCommander
synced 2025-12-12 22:33:17 +00:00
Fixed -kvmdatatrace issue.
This commit is contained in:
572
pki.js/common.js
Normal file
572
pki.js/common.js
Normal file
@@ -0,0 +1,572 @@
|
||||
import * as asn1js from "asn1js";
|
||||
import { utilConcatBuf } from "pvutils";
|
||||
import CryptoEngine from "./CryptoEngine.js";
|
||||
//**************************************************************************************
|
||||
//region Crypto engine related function
|
||||
//**************************************************************************************
|
||||
let engine = {
|
||||
name: "none",
|
||||
crypto: null,
|
||||
subtle: null
|
||||
};
|
||||
//**************************************************************************************
|
||||
export function setEngine(name, crypto, subtle)
|
||||
{
|
||||
//region We are in Node
|
||||
// noinspection JSUnresolvedVariable
|
||||
if((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined"))
|
||||
{
|
||||
// noinspection ES6ModulesDependencies, JSUnresolvedVariable
|
||||
if(typeof global[process.pid] === "undefined")
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
global[process.pid] = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
if(typeof global[process.pid] !== "object")
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
throw new Error(`Name global.${process.pid} already exists and it is not an object`);
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection JSUnresolvedVariable
|
||||
if(typeof global[process.pid].pkijs === "undefined")
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
global[process.pid].pkijs = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
if(typeof global[process.pid].pkijs !== "object")
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`);
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection JSUnresolvedVariable
|
||||
global[process.pid].pkijs.engine = {
|
||||
name: name,
|
||||
crypto: crypto,
|
||||
subtle: subtle
|
||||
};
|
||||
}
|
||||
//endregion
|
||||
//region We are in browser
|
||||
else
|
||||
{
|
||||
engine = {
|
||||
name: name,
|
||||
crypto: crypto,
|
||||
subtle: subtle
|
||||
};
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
//**************************************************************************************
|
||||
export function getEngine()
|
||||
{
|
||||
//region We are in Node
|
||||
// noinspection JSUnresolvedVariable
|
||||
if((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined"))
|
||||
{
|
||||
let _engine;
|
||||
|
||||
try
|
||||
{
|
||||
// noinspection JSUnresolvedVariable
|
||||
_engine = global[process.pid].pkijs.engine;
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
throw new Error("Please call \"setEngine\" before call to \"getEngine\"");
|
||||
}
|
||||
|
||||
return _engine;
|
||||
}
|
||||
//endregion
|
||||
|
||||
return engine;
|
||||
}
|
||||
//**************************************************************************************
|
||||
(function initCryptoEngine()
|
||||
{
|
||||
if(typeof self !== "undefined")
|
||||
{
|
||||
if("crypto" in self)
|
||||
{
|
||||
let engineName = "webcrypto";
|
||||
|
||||
/**
|
||||
* Standard crypto object
|
||||
* @type {Object}
|
||||
* @property {Object} [webkitSubtle] Subtle object from Apple
|
||||
*/
|
||||
const cryptoObject = self.crypto;
|
||||
let subtleObject;
|
||||
|
||||
// Apple Safari support
|
||||
if("webkitSubtle" in self.crypto)
|
||||
{
|
||||
try
|
||||
{
|
||||
subtleObject = self.crypto.webkitSubtle;
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
subtleObject = self.crypto.subtle;
|
||||
}
|
||||
|
||||
engineName = "safari";
|
||||
}
|
||||
|
||||
if("subtle" in self.crypto)
|
||||
subtleObject = self.crypto.subtle;
|
||||
|
||||
|
||||
if(typeof subtleObject === "undefined")
|
||||
{
|
||||
engine = {
|
||||
name: engineName,
|
||||
crypto: cryptoObject,
|
||||
subtle: null
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
engine = {
|
||||
name: engineName,
|
||||
crypto: cryptoObject,
|
||||
subtle: new CryptoEngine({name: engineName, crypto: self.crypto, subtle: subtleObject})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setEngine(engine.name, engine.crypto, engine.subtle);
|
||||
})();
|
||||
//**************************************************************************************
|
||||
//endregion
|
||||
//**************************************************************************************
|
||||
//region Declaration of common functions
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Get crypto subtle from current "crypto engine" or "undefined"
|
||||
* @returns {({decrypt, deriveKey, digest, encrypt, exportKey, generateKey, importKey, sign, unwrapKey, verify, wrapKey}|null)}
|
||||
*/
|
||||
export function getCrypto()
|
||||
{
|
||||
const _engine = getEngine();
|
||||
|
||||
if(_engine.subtle !== null)
|
||||
return _engine.subtle;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Initialize input Uint8Array by random values (with help from current "crypto engine")
|
||||
* @param {!Uint8Array} view
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getRandomValues(view)
|
||||
{
|
||||
return getEngine().subtle.getRandomValues(view);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Get OID for each specific algorithm
|
||||
* @param {Object} algorithm
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getOIDByAlgorithm(algorithm)
|
||||
{
|
||||
return getEngine().subtle.getOIDByAlgorithm(algorithm);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Get default algorithm parameters for each kind of operation
|
||||
* @param {string} algorithmName Algorithm name to get common parameters for
|
||||
* @param {string} operation Kind of operation: "sign", "encrypt", "generatekey", "importkey", "exportkey", "verify"
|
||||
* @returns {*}
|
||||
*/
|
||||
export function getAlgorithmParameters(algorithmName, operation)
|
||||
{
|
||||
return getEngine().subtle.getAlgorithmParameters(algorithmName, operation);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Create CMS ECDSA signature from WebCrypto ECDSA signature
|
||||
* @param {ArrayBuffer} signatureBuffer WebCrypto result of "sign" function
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
export function createCMSECDSASignature(signatureBuffer)
|
||||
{
|
||||
//region Initial check for correct length
|
||||
if((signatureBuffer.byteLength % 2) !== 0)
|
||||
return new ArrayBuffer(0);
|
||||
//endregion
|
||||
|
||||
//region Initial variables
|
||||
const length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer
|
||||
|
||||
const rBuffer = new ArrayBuffer(length);
|
||||
const rView = new Uint8Array(rBuffer);
|
||||
rView.set(new Uint8Array(signatureBuffer, 0, length));
|
||||
|
||||
const rInteger = new asn1js.Integer({ valueHex: rBuffer });
|
||||
|
||||
const sBuffer = new ArrayBuffer(length);
|
||||
const sView = new Uint8Array(sBuffer);
|
||||
sView.set(new Uint8Array(signatureBuffer, length, length));
|
||||
|
||||
const sInteger = new asn1js.Integer({ valueHex: sBuffer });
|
||||
//endregion
|
||||
|
||||
return (new asn1js.Sequence({
|
||||
value: [
|
||||
rInteger.convertToDER(),
|
||||
sInteger.convertToDER()
|
||||
]
|
||||
})).toBER(false);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* String preparation function. In a future here will be realization of algorithm from RFC4518
|
||||
* @param {string} inputString JavaScript string. As soon as for each ASN.1 string type we have a specific transformation function here we will work with pure JavaScript string
|
||||
* @returns {string} Formated string
|
||||
*/
|
||||
export function stringPrep(inputString)
|
||||
{
|
||||
//region Initial variables
|
||||
let isSpace = false;
|
||||
let cuttedResult = "";
|
||||
//endregion
|
||||
|
||||
const result = inputString.trim(); // Trim input string
|
||||
|
||||
//region Change all sequence of SPACE down to SPACE char
|
||||
for(let i = 0; i < result.length; i++)
|
||||
{
|
||||
if(result.charCodeAt(i) === 32)
|
||||
{
|
||||
if(isSpace === false)
|
||||
isSpace = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isSpace)
|
||||
{
|
||||
cuttedResult += " ";
|
||||
isSpace = false;
|
||||
}
|
||||
|
||||
cuttedResult += result[i];
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
return cuttedResult.toLowerCase();
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Create a single ArrayBuffer from CMS ECDSA signature
|
||||
* @param {Sequence} cmsSignature ASN.1 SEQUENCE contains CMS ECDSA signature
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
export function createECDSASignatureFromCMS(cmsSignature)
|
||||
{
|
||||
//region Check input variables
|
||||
if((cmsSignature instanceof asn1js.Sequence) === false)
|
||||
return new ArrayBuffer(0);
|
||||
|
||||
if(cmsSignature.valueBlock.value.length !== 2)
|
||||
return new ArrayBuffer(0);
|
||||
|
||||
if((cmsSignature.valueBlock.value[0] instanceof asn1js.Integer) === false)
|
||||
return new ArrayBuffer(0);
|
||||
|
||||
if((cmsSignature.valueBlock.value[1] instanceof asn1js.Integer) === false)
|
||||
return new ArrayBuffer(0);
|
||||
//endregion
|
||||
|
||||
const rValue = cmsSignature.valueBlock.value[0].convertFromDER();
|
||||
const sValue = cmsSignature.valueBlock.value[1].convertFromDER();
|
||||
|
||||
//region Check the lengths of two parts are equal
|
||||
switch(true)
|
||||
{
|
||||
case (rValue.valueBlock.valueHex.byteLength < sValue.valueBlock.valueHex.byteLength):
|
||||
{
|
||||
if((sValue.valueBlock.valueHex.byteLength - rValue.valueBlock.valueHex.byteLength) !== 1)
|
||||
throw new Error("Incorrect DER integer decoding");
|
||||
|
||||
const correctedLength = sValue.valueBlock.valueHex.byteLength;
|
||||
|
||||
const rValueView = new Uint8Array(rValue.valueBlock.valueHex);
|
||||
|
||||
const rValueBufferCorrected = new ArrayBuffer(correctedLength);
|
||||
const rValueViewCorrected = new Uint8Array(rValueBufferCorrected);
|
||||
|
||||
rValueViewCorrected.set(rValueView, 1);
|
||||
rValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
|
||||
|
||||
return utilConcatBuf(rValueBufferCorrected, sValue.valueBlock.valueHex);
|
||||
}
|
||||
case (rValue.valueBlock.valueHex.byteLength > sValue.valueBlock.valueHex.byteLength):
|
||||
{
|
||||
if((rValue.valueBlock.valueHex.byteLength - sValue.valueBlock.valueHex.byteLength) !== 1)
|
||||
throw new Error("Incorrect DER integer decoding");
|
||||
|
||||
const correctedLength = rValue.valueBlock.valueHex.byteLength;
|
||||
|
||||
const sValueView = new Uint8Array(sValue.valueBlock.valueHex);
|
||||
|
||||
const sValueBufferCorrected = new ArrayBuffer(correctedLength);
|
||||
const sValueViewCorrected = new Uint8Array(sValueBufferCorrected);
|
||||
|
||||
sValueViewCorrected.set(sValueView, 1);
|
||||
sValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
|
||||
|
||||
return utilConcatBuf(rValue.valueBlock.valueHex, sValueBufferCorrected);
|
||||
}
|
||||
default:
|
||||
{
|
||||
//region In case we have equal length and the length is not even with 2
|
||||
if(rValue.valueBlock.valueHex.byteLength % 2)
|
||||
{
|
||||
const correctedLength = (rValue.valueBlock.valueHex.byteLength + 1);
|
||||
|
||||
const rValueView = new Uint8Array(rValue.valueBlock.valueHex);
|
||||
|
||||
const rValueBufferCorrected = new ArrayBuffer(correctedLength);
|
||||
const rValueViewCorrected = new Uint8Array(rValueBufferCorrected);
|
||||
|
||||
rValueViewCorrected.set(rValueView, 1);
|
||||
rValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
|
||||
|
||||
const sValueView = new Uint8Array(sValue.valueBlock.valueHex);
|
||||
|
||||
const sValueBufferCorrected = new ArrayBuffer(correctedLength);
|
||||
const sValueViewCorrected = new Uint8Array(sValueBufferCorrected);
|
||||
|
||||
sValueViewCorrected.set(sValueView, 1);
|
||||
sValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
|
||||
|
||||
return utilConcatBuf(rValueBufferCorrected, sValueBufferCorrected);
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
return utilConcatBuf(rValue.valueBlock.valueHex, sValue.valueBlock.valueHex);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Get WebCrypto algorithm by wel-known OID
|
||||
* @param {string} oid well-known OID to search for
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getAlgorithmByOID(oid)
|
||||
{
|
||||
return getEngine().subtle.getAlgorithmByOID(oid);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* Getting hash algorithm by signature algorithm
|
||||
* @param {AlgorithmIdentifier} signatureAlgorithm Signature algorithm
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getHashAlgorithm(signatureAlgorithm)
|
||||
{
|
||||
return getEngine().subtle.getHashAlgorithm(signatureAlgorithm);
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* ANS X9.63 Key Derivation Function having a "Counter" as a parameter
|
||||
* @param {string} hashFunction Used hash function
|
||||
* @param {ArrayBuffer} Zbuffer ArrayBuffer containing ECDH shared secret to derive from
|
||||
* @param {number} Counter
|
||||
* @param {ArrayBuffer} SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
|
||||
*/
|
||||
export function kdfWithCounter(hashFunction, Zbuffer, Counter, SharedInfo)
|
||||
{
|
||||
//region Check of input parameters
|
||||
switch(hashFunction.toUpperCase())
|
||||
{
|
||||
case "SHA-1":
|
||||
case "SHA-256":
|
||||
case "SHA-384":
|
||||
case "SHA-512":
|
||||
break;
|
||||
default:
|
||||
return Promise.reject(`Unknown hash function: ${hashFunction}`);
|
||||
}
|
||||
|
||||
if((Zbuffer instanceof ArrayBuffer) === false)
|
||||
return Promise.reject("Please set \"Zbuffer\" as \"ArrayBuffer\"");
|
||||
|
||||
if(Zbuffer.byteLength === 0)
|
||||
return Promise.reject("\"Zbuffer\" has zero length, error");
|
||||
|
||||
if((SharedInfo instanceof ArrayBuffer) === false)
|
||||
return Promise.reject("Please set \"SharedInfo\" as \"ArrayBuffer\"");
|
||||
|
||||
if(Counter > 255)
|
||||
return Promise.reject("Please set \"Counter\" variable to value less or equal to 255");
|
||||
//endregion
|
||||
|
||||
//region Initial variables
|
||||
const counterBuffer = new ArrayBuffer(4);
|
||||
const counterView = new Uint8Array(counterBuffer);
|
||||
counterView[0] = 0x00;
|
||||
counterView[1] = 0x00;
|
||||
counterView[2] = 0x00;
|
||||
counterView[3] = Counter;
|
||||
|
||||
let combinedBuffer = new ArrayBuffer(0);
|
||||
//endregion
|
||||
|
||||
//region Get a "crypto" extension
|
||||
const crypto = getCrypto();
|
||||
if(typeof crypto === "undefined")
|
||||
return Promise.reject("Unable to create WebCrypto object");
|
||||
//endregion
|
||||
|
||||
//region Create a combined ArrayBuffer for digesting
|
||||
combinedBuffer = utilConcatBuf(combinedBuffer, Zbuffer);
|
||||
combinedBuffer = utilConcatBuf(combinedBuffer, counterBuffer);
|
||||
combinedBuffer = utilConcatBuf(combinedBuffer, SharedInfo);
|
||||
//endregion
|
||||
|
||||
//region Return digest of combined ArrayBuffer and information about current counter
|
||||
return crypto.digest({
|
||||
name: hashFunction
|
||||
},
|
||||
combinedBuffer)
|
||||
.then(result =>
|
||||
({
|
||||
counter: Counter,
|
||||
result
|
||||
}));
|
||||
//endregion
|
||||
}
|
||||
//**************************************************************************************
|
||||
/**
|
||||
* ANS X9.63 Key Derivation Function
|
||||
* @param {string} hashFunction Used hash function
|
||||
* @param {ArrayBuffer} Zbuffer ArrayBuffer containing ECDH shared secret to derive from
|
||||
* @param {number} keydatalen Length (!!! in BITS !!!) of used kew derivation function
|
||||
* @param {ArrayBuffer} SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
|
||||
*/
|
||||
export function kdf(hashFunction, Zbuffer, keydatalen, SharedInfo)
|
||||
{
|
||||
//region Initial variables
|
||||
let hashLength = 0;
|
||||
let maxCounter = 1;
|
||||
|
||||
const kdfArray = [];
|
||||
//endregion
|
||||
|
||||
//region Check of input parameters
|
||||
switch(hashFunction.toUpperCase())
|
||||
{
|
||||
case "SHA-1":
|
||||
hashLength = 160; // In bits
|
||||
break;
|
||||
case "SHA-256":
|
||||
hashLength = 256; // In bits
|
||||
break;
|
||||
case "SHA-384":
|
||||
hashLength = 384; // In bits
|
||||
break;
|
||||
case "SHA-512":
|
||||
hashLength = 512; // In bits
|
||||
break;
|
||||
default:
|
||||
return Promise.reject(`Unknown hash function: ${hashFunction}`);
|
||||
}
|
||||
|
||||
if((Zbuffer instanceof ArrayBuffer) === false)
|
||||
return Promise.reject("Please set \"Zbuffer\" as \"ArrayBuffer\"");
|
||||
|
||||
if(Zbuffer.byteLength === 0)
|
||||
return Promise.reject("\"Zbuffer\" has zero length, error");
|
||||
|
||||
if((SharedInfo instanceof ArrayBuffer) === false)
|
||||
return Promise.reject("Please set \"SharedInfo\" as \"ArrayBuffer\"");
|
||||
//endregion
|
||||
|
||||
//region Calculated maximum value of "Counter" variable
|
||||
const quotient = keydatalen / hashLength;
|
||||
|
||||
if(Math.floor(quotient) > 0)
|
||||
{
|
||||
maxCounter = Math.floor(quotient);
|
||||
|
||||
if((quotient - maxCounter) > 0)
|
||||
maxCounter++;
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Create an array of "kdfWithCounter"
|
||||
for(let i = 1; i <= maxCounter; i++)
|
||||
kdfArray.push(kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo));
|
||||
//endregion
|
||||
|
||||
//region Return combined digest with specified length
|
||||
return Promise.all(kdfArray).then(incomingResult =>
|
||||
{
|
||||
//region Initial variables
|
||||
let combinedBuffer = new ArrayBuffer(0);
|
||||
let currentCounter = 1;
|
||||
let found = true;
|
||||
//endregion
|
||||
|
||||
//region Combine all buffer together
|
||||
while(found)
|
||||
{
|
||||
found = false;
|
||||
|
||||
for(const result of incomingResult)
|
||||
{
|
||||
if(result.counter === currentCounter)
|
||||
{
|
||||
combinedBuffer = utilConcatBuf(combinedBuffer, result.result);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
currentCounter++;
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Create output buffer with specified length
|
||||
keydatalen >>= 3; // Divide by 8 since "keydatalen" is in bits
|
||||
|
||||
if(combinedBuffer.byteLength > keydatalen)
|
||||
{
|
||||
const newBuffer = new ArrayBuffer(keydatalen);
|
||||
const newView = new Uint8Array(newBuffer);
|
||||
const combinedView = new Uint8Array(combinedBuffer);
|
||||
|
||||
for(let i = 0; i < keydatalen; i++)
|
||||
newView[i] = combinedView[i];
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
return combinedBuffer; // Since the situation when "combinedBuffer.byteLength < keydatalen" here we have only "combinedBuffer.byteLength === keydatalen"
|
||||
//endregion
|
||||
});
|
||||
//endregion
|
||||
}
|
||||
//**************************************************************************************
|
||||
//endregion
|
||||
//**************************************************************************************
|
||||
Reference in New Issue
Block a user