mirror of
https://github.com/Ylianst/MeshCommander
synced 2025-12-06 06:03:20 +00:00
2566 lines
66 KiB
JavaScript
2566 lines
66 KiB
JavaScript
import * as asn1js from "asn1js";
|
|
import { getParametersValue, stringToArrayBuffer, arrayBufferToString, utilConcatBuf } from "pvutils";
|
|
import { createCMSECDSASignature, createECDSASignatureFromCMS } from "./common.js";
|
|
import PublicKeyInfo from "./PublicKeyInfo.js";
|
|
import PrivateKeyInfo from "./PrivateKeyInfo.js";
|
|
import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
|
|
import EncryptedContentInfo from "./EncryptedContentInfo.js";
|
|
import RSASSAPSSParams from "./RSASSAPSSParams.js";
|
|
import PBKDF2Params from "./PBKDF2Params.js";
|
|
import PBES2Params from "./PBES2Params.js";
|
|
//**************************************************************************************
|
|
/**
|
|
* Making MAC key using algorithm described in B.2 of PKCS#12 standard.
|
|
*/
|
|
function makePKCS12B2Key(cryptoEngine, hashAlgorithm, keyLength, password, salt, iterationCount)
|
|
{
|
|
//region Initial variables
|
|
let u;
|
|
let v;
|
|
|
|
const result = [];
|
|
//endregion
|
|
|
|
//region Get "u" and "v" values
|
|
switch(hashAlgorithm.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
u = 20; // 160
|
|
v = 64; // 512
|
|
break;
|
|
case "SHA-256":
|
|
u = 32; // 256
|
|
v = 64; // 512
|
|
break;
|
|
case "SHA-384":
|
|
u = 48; // 384
|
|
v = 128; // 1024
|
|
break;
|
|
case "SHA-512":
|
|
u = 64; // 512
|
|
v = 128; // 1024
|
|
break;
|
|
default:
|
|
throw new Error("Unsupported hashing algorithm");
|
|
}
|
|
//endregion
|
|
|
|
//region Main algorithm making key
|
|
//region Transform password to UTF-8 like string
|
|
const passwordViewInitial = new Uint8Array(password);
|
|
|
|
const passwordTransformed = new ArrayBuffer((password.byteLength * 2) + 2);
|
|
const passwordTransformedView = new Uint8Array(passwordTransformed);
|
|
|
|
for(let i = 0; i < passwordViewInitial.length; i++)
|
|
{
|
|
passwordTransformedView[i * 2] = 0x00;
|
|
passwordTransformedView[i * 2 + 1] = passwordViewInitial[i];
|
|
}
|
|
|
|
passwordTransformedView[passwordTransformedView.length - 2] = 0x00;
|
|
passwordTransformedView[passwordTransformedView.length - 1] = 0x00;
|
|
|
|
password = passwordTransformed.slice(0);
|
|
//endregion
|
|
|
|
//region Construct a string D (the "diversifier") by concatenating v/8 copies of ID
|
|
const D = new ArrayBuffer(v);
|
|
const dView = new Uint8Array(D);
|
|
|
|
for(let i = 0; i < D.byteLength; i++)
|
|
dView[i] = 3; // The ID value equal to "3" for MACing (see B.3 of standard)
|
|
//endregion
|
|
|
|
//region Concatenate copies of the salt together to create a string S of length v * ceil(s / v) bytes (the final copy of the salt may be trunacted to create S)
|
|
const saltLength = salt.byteLength;
|
|
|
|
const sLen = v * Math.ceil(saltLength / v);
|
|
const S = new ArrayBuffer(sLen);
|
|
const sView = new Uint8Array(S);
|
|
|
|
const saltView = new Uint8Array(salt);
|
|
|
|
for(let i = 0; i < sLen; i++)
|
|
sView[i] = saltView[i % saltLength];
|
|
//endregion
|
|
|
|
//region Concatenate copies of the password together to create a string P of length v * ceil(p / v) bytes (the final copy of the password may be truncated to create P)
|
|
const passwordLength = password.byteLength;
|
|
|
|
const pLen = v * Math.ceil(passwordLength / v);
|
|
const P = new ArrayBuffer(pLen);
|
|
const pView = new Uint8Array(P);
|
|
|
|
const passwordView = new Uint8Array(password);
|
|
|
|
for(let i = 0; i < pLen; i++)
|
|
pView[i] = passwordView[i % passwordLength];
|
|
//endregion
|
|
|
|
//region Set I=S||P to be the concatenation of S and P
|
|
const sPlusPLength = S.byteLength + P.byteLength;
|
|
|
|
let I = new ArrayBuffer(sPlusPLength);
|
|
let iView = new Uint8Array(I);
|
|
|
|
iView.set(sView);
|
|
iView.set(pView, sView.length);
|
|
//endregion
|
|
|
|
//region Set c=ceil(n / u)
|
|
const c = Math.ceil((keyLength >> 3) / u);
|
|
//endregion
|
|
|
|
//region Initial variables
|
|
let internalSequence = Promise.resolve(I);
|
|
//endregion
|
|
|
|
//region For i=1, 2, ..., c, do the following:
|
|
for(let i = 0; i <= c; i++)
|
|
{
|
|
internalSequence = internalSequence.then(_I =>
|
|
{
|
|
//region Create contecanetion of D and I
|
|
const dAndI = new ArrayBuffer(D.byteLength + _I.byteLength);
|
|
const dAndIView = new Uint8Array(dAndI);
|
|
|
|
dAndIView.set(dView);
|
|
dAndIView.set(iView, dView.length);
|
|
//endregion
|
|
|
|
return dAndI;
|
|
});
|
|
|
|
//region Make "iterationCount" rounds of hashing
|
|
for(let j = 0; j < iterationCount; j++)
|
|
internalSequence = internalSequence.then(roundBuffer => cryptoEngine.digest({ name: hashAlgorithm }, new Uint8Array(roundBuffer)));
|
|
//endregion
|
|
|
|
internalSequence = internalSequence.then(roundBuffer =>
|
|
{
|
|
//region Concatenate copies of Ai to create a string B of length v bits (the final copy of Ai may be truncated to create B)
|
|
const B = new ArrayBuffer(v);
|
|
const bView = new Uint8Array(B);
|
|
|
|
for(let j = 0; j < B.byteLength; j++)
|
|
bView[j] = roundBuffer[j % roundBuffer.length];
|
|
//endregion
|
|
|
|
//region Make new I value
|
|
const k = Math.ceil(saltLength / v) + Math.ceil(passwordLength / v);
|
|
const iRound = [];
|
|
|
|
let sliceStart = 0;
|
|
let sliceLength = v;
|
|
|
|
for(let j = 0; j < k; j++)
|
|
{
|
|
const chunk = Array.from(new Uint8Array(I.slice(sliceStart, sliceStart + sliceLength)));
|
|
sliceStart += v;
|
|
if((sliceStart + v) > I.byteLength)
|
|
sliceLength = I.byteLength - sliceStart;
|
|
|
|
let x = 0x1ff;
|
|
|
|
for(let l = (B.byteLength - 1); l >= 0; l--)
|
|
{
|
|
x >>= 8;
|
|
x += bView[l] + chunk[l];
|
|
chunk[l] = (x & 0xff);
|
|
}
|
|
|
|
iRound.push(...chunk);
|
|
}
|
|
|
|
I = new ArrayBuffer(iRound.length);
|
|
iView = new Uint8Array(I);
|
|
|
|
iView.set(iRound);
|
|
//endregion
|
|
|
|
result.push(...(new Uint8Array(roundBuffer)));
|
|
|
|
return I;
|
|
});
|
|
}
|
|
//endregion
|
|
|
|
//region Initialize final key
|
|
internalSequence = internalSequence.then(() =>
|
|
{
|
|
const resultBuffer = new ArrayBuffer(keyLength >> 3);
|
|
const resultView = new Uint8Array(resultBuffer);
|
|
|
|
resultView.set((new Uint8Array(result)).slice(0, keyLength >> 3));
|
|
|
|
return resultBuffer;
|
|
});
|
|
//endregion
|
|
//endregion
|
|
|
|
return internalSequence;
|
|
}
|
|
//**************************************************************************************
|
|
/**
|
|
* Default cryptographic engine for Web Cryptography API
|
|
*/
|
|
export default class CryptoEngine
|
|
{
|
|
//**********************************************************************************
|
|
/**
|
|
* Constructor for CryptoEngine class
|
|
* @param {Object} [parameters={}]
|
|
* @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
|
|
*/
|
|
constructor(parameters = {})
|
|
{
|
|
//region Internal properties of the object
|
|
/**
|
|
* @type {Object}
|
|
* @desc Usually here we are expecting "window.crypto" or an equivalent from custom "crypto engine"
|
|
*/
|
|
this.crypto = getParametersValue(parameters, "crypto", {});
|
|
/**
|
|
* @type {Object}
|
|
* @desc Usually here we are expecting "window.crypto.subtle" or an equivalent from custom "crypto engine"
|
|
*/
|
|
this.subtle = getParametersValue(parameters, "subtle", {});
|
|
/**
|
|
* @type {string}
|
|
* @desc Name of the "crypto engine"
|
|
*/
|
|
this.name = getParametersValue(parameters, "name", "");
|
|
//endregion
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Import WebCrypto keys from different formats
|
|
* @param {string} format
|
|
* @param {ArrayBuffer|Uint8Array} keyData
|
|
* @param {Object} algorithm
|
|
* @param {boolean} extractable
|
|
* @param {Array} keyUsages
|
|
* @returns {Promise}
|
|
*/
|
|
importKey(format, keyData, algorithm, extractable, keyUsages)
|
|
{
|
|
//region Initial variables
|
|
let jwk = {};
|
|
//endregion
|
|
|
|
//region Change "keyData" type if needed
|
|
if(keyData instanceof Uint8Array)
|
|
keyData = keyData.buffer;
|
|
//endregion
|
|
|
|
switch(format.toLowerCase())
|
|
{
|
|
case "raw":
|
|
return this.subtle.importKey("raw", keyData, algorithm, extractable, keyUsages);
|
|
case "spki":
|
|
{
|
|
const asn1 = asn1js.fromBER(keyData);
|
|
if(asn1.offset === (-1))
|
|
return Promise.reject("Incorrect keyData");
|
|
|
|
const publicKeyInfo = new PublicKeyInfo();
|
|
try
|
|
{
|
|
publicKeyInfo.fromSchema(asn1.result);
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject("Incorrect keyData");
|
|
}
|
|
|
|
|
|
// noinspection FallThroughInSwitchStatementJS
|
|
switch(algorithm.name.toUpperCase())
|
|
{
|
|
case "RSA-PSS":
|
|
{
|
|
//region Get information about used hash function
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
jwk.alg = "PS1";
|
|
break;
|
|
case "SHA-256":
|
|
jwk.alg = "PS256";
|
|
break;
|
|
case "SHA-384":
|
|
jwk.alg = "PS384";
|
|
break;
|
|
case "SHA-512":
|
|
jwk.alg = "PS512";
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
|
|
}
|
|
//endregion
|
|
}
|
|
// break omitted
|
|
case "RSASSA-PKCS1-V1_5":
|
|
{
|
|
keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
|
|
|
|
jwk.kty = "RSA";
|
|
jwk.ext = extractable;
|
|
jwk.key_ops = keyUsages;
|
|
|
|
if(publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1")
|
|
return Promise.reject(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
|
|
|
|
//region Get information about used hash function
|
|
if(("alg" in jwk) === false)
|
|
{
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
jwk.alg = "RS1";
|
|
break;
|
|
case "SHA-256":
|
|
jwk.alg = "RS256";
|
|
break;
|
|
case "SHA-384":
|
|
jwk.alg = "RS384";
|
|
break;
|
|
case "SHA-512":
|
|
jwk.alg = "RS512";
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Create RSA Public Key elements
|
|
const publicKeyJSON = publicKeyInfo.toJSON();
|
|
|
|
for(const key of Object.keys(publicKeyJSON))
|
|
jwk[key] = publicKeyJSON[key];
|
|
//endregion
|
|
}
|
|
break;
|
|
case "ECDSA":
|
|
keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
|
|
// break omitted
|
|
case "ECDH":
|
|
{
|
|
//region Initial variables
|
|
jwk = {
|
|
kty: "EC",
|
|
ext: extractable,
|
|
key_ops: keyUsages
|
|
};
|
|
//endregion
|
|
|
|
//region Get information about algorithm
|
|
if(publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1")
|
|
return Promise.reject(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
|
|
//endregion
|
|
|
|
//region Create ECDSA Public Key elements
|
|
const publicKeyJSON = publicKeyInfo.toJSON();
|
|
|
|
for(const key of Object.keys(publicKeyJSON))
|
|
jwk[key] = publicKeyJSON[key];
|
|
//endregion
|
|
}
|
|
break;
|
|
case "RSA-OAEP":
|
|
{
|
|
jwk.kty = "RSA";
|
|
jwk.ext = extractable;
|
|
jwk.key_ops = keyUsages;
|
|
|
|
if(this.name.toLowerCase() === "safari")
|
|
jwk.alg = "RSA-OAEP";
|
|
else
|
|
{
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
jwk.alg = "RSA-OAEP";
|
|
break;
|
|
case "SHA-256":
|
|
jwk.alg = "RSA-OAEP-256";
|
|
break;
|
|
case "SHA-384":
|
|
jwk.alg = "RSA-OAEP-384";
|
|
break;
|
|
case "SHA-512":
|
|
jwk.alg = "RSA-OAEP-512";
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
|
|
}
|
|
}
|
|
|
|
//region Create ECDSA Public Key elements
|
|
const publicKeyJSON = publicKeyInfo.toJSON();
|
|
|
|
for(const key of Object.keys(publicKeyJSON))
|
|
jwk[key] = publicKeyJSON[key];
|
|
//endregion
|
|
}
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect algorithm name: ${algorithm.name.toUpperCase()}`);
|
|
}
|
|
}
|
|
break;
|
|
case "pkcs8":
|
|
{
|
|
const privateKeyInfo = new PrivateKeyInfo();
|
|
|
|
//region Parse "PrivateKeyInfo" object
|
|
const asn1 = asn1js.fromBER(keyData);
|
|
if(asn1.offset === (-1))
|
|
return Promise.reject("Incorrect keyData");
|
|
|
|
try
|
|
{
|
|
privateKeyInfo.fromSchema(asn1.result);
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject("Incorrect keyData");
|
|
}
|
|
|
|
if(("parsedKey" in privateKeyInfo) === false)
|
|
return Promise.reject("Incorrect keyData");
|
|
//endregion
|
|
|
|
// noinspection FallThroughInSwitchStatementJS
|
|
// noinspection FallThroughInSwitchStatementJS
|
|
switch(algorithm.name.toUpperCase())
|
|
{
|
|
case "RSA-PSS":
|
|
{
|
|
//region Get information about used hash function
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
jwk.alg = "PS1";
|
|
break;
|
|
case "SHA-256":
|
|
jwk.alg = "PS256";
|
|
break;
|
|
case "SHA-384":
|
|
jwk.alg = "PS384";
|
|
break;
|
|
case "SHA-512":
|
|
jwk.alg = "PS512";
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
|
|
}
|
|
//endregion
|
|
}
|
|
// break omitted
|
|
case "RSASSA-PKCS1-V1_5":
|
|
{
|
|
keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
|
|
|
|
jwk.kty = "RSA";
|
|
jwk.ext = extractable;
|
|
jwk.key_ops = keyUsages;
|
|
|
|
//region Get information about used hash function
|
|
if(privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1")
|
|
return Promise.reject(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
|
|
//endregion
|
|
|
|
//region Get information about used hash function
|
|
if(("alg" in jwk) === false)
|
|
{
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
jwk.alg = "RS1";
|
|
break;
|
|
case "SHA-256":
|
|
jwk.alg = "RS256";
|
|
break;
|
|
case "SHA-384":
|
|
jwk.alg = "RS384";
|
|
break;
|
|
case "SHA-512":
|
|
jwk.alg = "RS512";
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Create RSA Private Key elements
|
|
const privateKeyJSON = privateKeyInfo.toJSON();
|
|
|
|
for(const key of Object.keys(privateKeyJSON))
|
|
jwk[key] = privateKeyJSON[key];
|
|
//endregion
|
|
}
|
|
break;
|
|
case "ECDSA":
|
|
keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
|
|
// break omitted
|
|
case "ECDH":
|
|
{
|
|
//region Initial variables
|
|
jwk = {
|
|
kty: "EC",
|
|
ext: extractable,
|
|
key_ops: keyUsages
|
|
};
|
|
//endregion
|
|
|
|
//region Get information about used hash function
|
|
if(privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1")
|
|
return Promise.reject(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
|
|
//endregion
|
|
|
|
//region Create ECDSA Private Key elements
|
|
const privateKeyJSON = privateKeyInfo.toJSON();
|
|
|
|
for(const key of Object.keys(privateKeyJSON))
|
|
jwk[key] = privateKeyJSON[key];
|
|
//endregion
|
|
}
|
|
break;
|
|
case "RSA-OAEP":
|
|
{
|
|
jwk.kty = "RSA";
|
|
jwk.ext = extractable;
|
|
jwk.key_ops = keyUsages;
|
|
|
|
//region Get information about used hash function
|
|
if(this.name.toLowerCase() === "safari")
|
|
jwk.alg = "RSA-OAEP";
|
|
else
|
|
{
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
jwk.alg = "RSA-OAEP";
|
|
break;
|
|
case "SHA-256":
|
|
jwk.alg = "RSA-OAEP-256";
|
|
break;
|
|
case "SHA-384":
|
|
jwk.alg = "RSA-OAEP-384";
|
|
break;
|
|
case "SHA-512":
|
|
jwk.alg = "RSA-OAEP-512";
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Create RSA Private Key elements
|
|
const privateKeyJSON = privateKeyInfo.toJSON();
|
|
|
|
for(const key of Object.keys(privateKeyJSON))
|
|
jwk[key] = privateKeyJSON[key];
|
|
//endregion
|
|
}
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect algorithm name: ${algorithm.name.toUpperCase()}`);
|
|
}
|
|
}
|
|
break;
|
|
case "jwk":
|
|
jwk = keyData;
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect format: ${format}`);
|
|
}
|
|
|
|
//region Special case for Safari browser (since its acting not as WebCrypto standard describes)
|
|
if(this.name.toLowerCase() === "safari")
|
|
{
|
|
// Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview)
|
|
return Promise.resolve().then(() => this.subtle.importKey("jwk", stringToArrayBuffer(JSON.stringify(jwk)), algorithm, extractable, keyUsages))
|
|
.then(result => result, () => this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages));
|
|
}
|
|
//endregion
|
|
|
|
return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Export WebCrypto keys to different formats
|
|
* @param {string} format
|
|
* @param {Object} key
|
|
* @returns {Promise}
|
|
*/
|
|
exportKey(format, key)
|
|
{
|
|
let sequence = this.subtle.exportKey("jwk", key);
|
|
|
|
//region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation
|
|
if(this.name.toLowerCase() === "safari")
|
|
{
|
|
sequence = sequence.then(result =>
|
|
{
|
|
// Some additional checks for Safari Technology Preview
|
|
if(result instanceof ArrayBuffer)
|
|
return JSON.parse(arrayBufferToString(result));
|
|
|
|
return result;
|
|
});
|
|
}
|
|
//endregion
|
|
|
|
switch(format.toLowerCase())
|
|
{
|
|
case "raw":
|
|
return this.subtle.exportKey("raw", key);
|
|
case "spki":
|
|
sequence = sequence.then(result =>
|
|
{
|
|
const publicKeyInfo = new PublicKeyInfo();
|
|
|
|
try
|
|
{
|
|
publicKeyInfo.fromJSON(result);
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject("Incorrect key data");
|
|
}
|
|
|
|
return publicKeyInfo.toSchema().toBER(false);
|
|
});
|
|
break;
|
|
case "pkcs8":
|
|
sequence = sequence.then(result =>
|
|
{
|
|
const privateKeyInfo = new PrivateKeyInfo();
|
|
|
|
try
|
|
{
|
|
privateKeyInfo.fromJSON(result);
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject("Incorrect key data");
|
|
}
|
|
|
|
return privateKeyInfo.toSchema().toBER(false);
|
|
});
|
|
break;
|
|
case "jwk":
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect format: ${format}`);
|
|
}
|
|
|
|
return sequence;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Convert WebCrypto keys between different export formats
|
|
* @param {string} inputFormat
|
|
* @param {string} outputFormat
|
|
* @param {ArrayBuffer|Object} keyData
|
|
* @param {Object} algorithm
|
|
* @param {boolean} extractable
|
|
* @param {Array} keyUsages
|
|
* @returns {Promise}
|
|
*/
|
|
convert(inputFormat, outputFormat, keyData, algorithm, extractable, keyUsages)
|
|
{
|
|
switch(inputFormat.toLowerCase())
|
|
{
|
|
case "raw":
|
|
switch(outputFormat.toLowerCase())
|
|
{
|
|
case "raw":
|
|
return Promise.resolve(keyData);
|
|
case "spki":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("spki", result));
|
|
case "pkcs8":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("pkcs8", result));
|
|
case "jwk":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("jwk", result));
|
|
default:
|
|
return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
|
|
}
|
|
case "spki":
|
|
switch(outputFormat.toLowerCase())
|
|
{
|
|
case "raw":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("spki", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("raw", result));
|
|
case "spki":
|
|
return Promise.resolve(keyData);
|
|
case "pkcs8":
|
|
return Promise.reject("Impossible to convert between SPKI/PKCS8");
|
|
case "jwk":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("spki", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("jwk", result));
|
|
default:
|
|
return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
|
|
}
|
|
case "pkcs8":
|
|
switch(outputFormat.toLowerCase())
|
|
{
|
|
case "raw":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("pkcs8", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("raw", result));
|
|
case "spki":
|
|
return Promise.reject("Impossible to convert between SPKI/PKCS8");
|
|
case "pkcs8":
|
|
return Promise.resolve(keyData);
|
|
case "jwk":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("pkcs8", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("jwk", result));
|
|
default:
|
|
return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
|
|
}
|
|
case "jwk":
|
|
switch(outputFormat.toLowerCase())
|
|
{
|
|
case "raw":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("raw", result));
|
|
case "spki":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("spki", result));
|
|
case "pkcs8":
|
|
return Promise.resolve()
|
|
.then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages))
|
|
.then(result => this.exportKey("pkcs8", result));
|
|
case "jwk":
|
|
return Promise.resolve(keyData);
|
|
default:
|
|
return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
|
|
}
|
|
default:
|
|
return Promise.reject(`Incorrect inputFormat: ${inputFormat}`);
|
|
}
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "encrypt"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
encrypt(...args)
|
|
{
|
|
return this.subtle.encrypt(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "decrypt"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
decrypt(...args)
|
|
{
|
|
return this.subtle.decrypt(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "sign"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
sign(...args)
|
|
{
|
|
return this.subtle.sign(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "verify"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
verify(...args)
|
|
{
|
|
return this.subtle.verify(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "digest"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
digest(...args)
|
|
{
|
|
return this.subtle.digest(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "generateKey"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
generateKey(...args)
|
|
{
|
|
return this.subtle.generateKey(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "deriveKey"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
deriveKey(...args)
|
|
{
|
|
return this.subtle.deriveKey(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "deriveBits"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
deriveBits(...args)
|
|
{
|
|
return this.subtle.deriveBits(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "wrapKey"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
wrapKey(...args)
|
|
{
|
|
return this.subtle.wrapKey(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Wrapper for standard function "unwrapKey"
|
|
* @param args
|
|
* @returns {Promise}
|
|
*/
|
|
unwrapKey(...args)
|
|
{
|
|
return this.subtle.unwrapKey(...args);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Initialize input Uint8Array by random values (with help from current "crypto engine")
|
|
* @param {!Uint8Array} view
|
|
* @returns {*}
|
|
*/
|
|
getRandomValues(view)
|
|
{
|
|
if(("getRandomValues" in this.crypto) === false)
|
|
throw new Error("No support for getRandomValues");
|
|
|
|
return this.crypto.getRandomValues(view);
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Get WebCrypto algorithm by wel-known OID
|
|
* @param {string} oid well-known OID to search for
|
|
* @returns {Object}
|
|
*/
|
|
getAlgorithmByOID(oid)
|
|
{
|
|
switch(oid)
|
|
{
|
|
case "1.2.840.113549.1.1.1":
|
|
case "1.2.840.113549.1.1.5":
|
|
return {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
hash: {
|
|
name: "SHA-1"
|
|
}
|
|
};
|
|
case "1.2.840.113549.1.1.11":
|
|
return {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
};
|
|
case "1.2.840.113549.1.1.12":
|
|
return {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
hash: {
|
|
name: "SHA-384"
|
|
}
|
|
};
|
|
case "1.2.840.113549.1.1.13":
|
|
return {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
hash: {
|
|
name: "SHA-512"
|
|
}
|
|
};
|
|
case "1.2.840.113549.1.1.10":
|
|
return {
|
|
name: "RSA-PSS"
|
|
};
|
|
case "1.2.840.113549.1.1.7":
|
|
return {
|
|
name: "RSA-OAEP"
|
|
};
|
|
case "1.2.840.10045.2.1":
|
|
case "1.2.840.10045.4.1":
|
|
return {
|
|
name: "ECDSA",
|
|
hash: {
|
|
name: "SHA-1"
|
|
}
|
|
};
|
|
case "1.2.840.10045.4.3.2":
|
|
return {
|
|
name: "ECDSA",
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
};
|
|
case "1.2.840.10045.4.3.3":
|
|
return {
|
|
name: "ECDSA",
|
|
hash: {
|
|
name: "SHA-384"
|
|
}
|
|
};
|
|
case "1.2.840.10045.4.3.4":
|
|
return {
|
|
name: "ECDSA",
|
|
hash: {
|
|
name: "SHA-512"
|
|
}
|
|
};
|
|
case "1.3.133.16.840.63.0.2":
|
|
return {
|
|
name: "ECDH",
|
|
kdf: "SHA-1"
|
|
};
|
|
case "1.3.132.1.11.1":
|
|
return {
|
|
name: "ECDH",
|
|
kdf: "SHA-256"
|
|
};
|
|
case "1.3.132.1.11.2":
|
|
return {
|
|
name: "ECDH",
|
|
kdf: "SHA-384"
|
|
};
|
|
case "1.3.132.1.11.3":
|
|
return {
|
|
name: "ECDH",
|
|
kdf: "SHA-512"
|
|
};
|
|
case "2.16.840.1.101.3.4.1.2":
|
|
return {
|
|
name: "AES-CBC",
|
|
length: 128
|
|
};
|
|
case "2.16.840.1.101.3.4.1.22":
|
|
return {
|
|
name: "AES-CBC",
|
|
length: 192
|
|
};
|
|
case "2.16.840.1.101.3.4.1.42":
|
|
return {
|
|
name: "AES-CBC",
|
|
length: 256
|
|
};
|
|
case "2.16.840.1.101.3.4.1.6":
|
|
return {
|
|
name: "AES-GCM",
|
|
length: 128
|
|
};
|
|
case "2.16.840.1.101.3.4.1.26":
|
|
return {
|
|
name: "AES-GCM",
|
|
length: 192
|
|
};
|
|
case "2.16.840.1.101.3.4.1.46":
|
|
return {
|
|
name: "AES-GCM",
|
|
length: 256
|
|
};
|
|
case "2.16.840.1.101.3.4.1.4":
|
|
return {
|
|
name: "AES-CFB",
|
|
length: 128
|
|
};
|
|
case "2.16.840.1.101.3.4.1.24":
|
|
return {
|
|
name: "AES-CFB",
|
|
length: 192
|
|
};
|
|
case "2.16.840.1.101.3.4.1.44":
|
|
return {
|
|
name: "AES-CFB",
|
|
length: 256
|
|
};
|
|
case "2.16.840.1.101.3.4.1.5":
|
|
return {
|
|
name: "AES-KW",
|
|
length: 128
|
|
};
|
|
case "2.16.840.1.101.3.4.1.25":
|
|
return {
|
|
name: "AES-KW",
|
|
length: 192
|
|
};
|
|
case "2.16.840.1.101.3.4.1.45":
|
|
return {
|
|
name: "AES-KW",
|
|
length: 256
|
|
};
|
|
case "1.2.840.113549.2.7":
|
|
return {
|
|
name: "HMAC",
|
|
hash: {
|
|
name: "SHA-1"
|
|
}
|
|
};
|
|
case "1.2.840.113549.2.9":
|
|
return {
|
|
name: "HMAC",
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
};
|
|
case "1.2.840.113549.2.10":
|
|
return {
|
|
name: "HMAC",
|
|
hash: {
|
|
name: "SHA-384"
|
|
}
|
|
};
|
|
case "1.2.840.113549.2.11":
|
|
return {
|
|
name: "HMAC",
|
|
hash: {
|
|
name: "SHA-512"
|
|
}
|
|
};
|
|
case "1.2.840.113549.1.9.16.3.5":
|
|
return {
|
|
name: "DH"
|
|
};
|
|
case "1.3.14.3.2.26":
|
|
return {
|
|
name: "SHA-1"
|
|
};
|
|
case "2.16.840.1.101.3.4.2.1":
|
|
return {
|
|
name: "SHA-256"
|
|
};
|
|
case "2.16.840.1.101.3.4.2.2":
|
|
return {
|
|
name: "SHA-384"
|
|
};
|
|
case "2.16.840.1.101.3.4.2.3":
|
|
return {
|
|
name: "SHA-512"
|
|
};
|
|
case "1.2.840.113549.1.5.12":
|
|
return {
|
|
name: "PBKDF2"
|
|
};
|
|
//region Special case - OIDs for ECC curves
|
|
case "1.2.840.10045.3.1.7":
|
|
return {
|
|
name: "P-256"
|
|
};
|
|
case "1.3.132.0.34":
|
|
return {
|
|
name: "P-384"
|
|
};
|
|
case "1.3.132.0.35":
|
|
return {
|
|
name: "P-521"
|
|
};
|
|
//endregion
|
|
default:
|
|
}
|
|
|
|
return {};
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Get OID for each specific algorithm
|
|
* @param {Object} algorithm
|
|
* @returns {string}
|
|
*/
|
|
getOIDByAlgorithm(algorithm)
|
|
{
|
|
let result = "";
|
|
|
|
switch(algorithm.name.toUpperCase())
|
|
{
|
|
case "RSASSA-PKCS1-V1_5":
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
result = "1.2.840.113549.1.1.5";
|
|
break;
|
|
case "SHA-256":
|
|
result = "1.2.840.113549.1.1.11";
|
|
break;
|
|
case "SHA-384":
|
|
result = "1.2.840.113549.1.1.12";
|
|
break;
|
|
case "SHA-512":
|
|
result = "1.2.840.113549.1.1.13";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "RSA-PSS":
|
|
result = "1.2.840.113549.1.1.10";
|
|
break;
|
|
case "RSA-OAEP":
|
|
result = "1.2.840.113549.1.1.7";
|
|
break;
|
|
case "ECDSA":
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
result = "1.2.840.10045.4.1";
|
|
break;
|
|
case "SHA-256":
|
|
result = "1.2.840.10045.4.3.2";
|
|
break;
|
|
case "SHA-384":
|
|
result = "1.2.840.10045.4.3.3";
|
|
break;
|
|
case "SHA-512":
|
|
result = "1.2.840.10045.4.3.4";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "ECDH":
|
|
switch(algorithm.kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function
|
|
{
|
|
case "SHA-1":
|
|
result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme
|
|
break;
|
|
case "SHA-256":
|
|
result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme
|
|
break;
|
|
case "SHA-384":
|
|
result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme
|
|
break;
|
|
case "SHA-512":
|
|
result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "AES-CTR":
|
|
break;
|
|
case "AES-CBC":
|
|
switch(algorithm.length)
|
|
{
|
|
case 128:
|
|
result = "2.16.840.1.101.3.4.1.2";
|
|
break;
|
|
case 192:
|
|
result = "2.16.840.1.101.3.4.1.22";
|
|
break;
|
|
case 256:
|
|
result = "2.16.840.1.101.3.4.1.42";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "AES-CMAC":
|
|
break;
|
|
case "AES-GCM":
|
|
switch(algorithm.length)
|
|
{
|
|
case 128:
|
|
result = "2.16.840.1.101.3.4.1.6";
|
|
break;
|
|
case 192:
|
|
result = "2.16.840.1.101.3.4.1.26";
|
|
break;
|
|
case 256:
|
|
result = "2.16.840.1.101.3.4.1.46";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "AES-CFB":
|
|
switch(algorithm.length)
|
|
{
|
|
case 128:
|
|
result = "2.16.840.1.101.3.4.1.4";
|
|
break;
|
|
case 192:
|
|
result = "2.16.840.1.101.3.4.1.24";
|
|
break;
|
|
case 256:
|
|
result = "2.16.840.1.101.3.4.1.44";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "AES-KW":
|
|
switch(algorithm.length)
|
|
{
|
|
case 128:
|
|
result = "2.16.840.1.101.3.4.1.5";
|
|
break;
|
|
case 192:
|
|
result = "2.16.840.1.101.3.4.1.25";
|
|
break;
|
|
case 256:
|
|
result = "2.16.840.1.101.3.4.1.45";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "HMAC":
|
|
switch(algorithm.hash.name.toUpperCase())
|
|
{
|
|
case "SHA-1":
|
|
result = "1.2.840.113549.2.7";
|
|
break;
|
|
case "SHA-256":
|
|
result = "1.2.840.113549.2.9";
|
|
break;
|
|
case "SHA-384":
|
|
result = "1.2.840.113549.2.10";
|
|
break;
|
|
case "SHA-512":
|
|
result = "1.2.840.113549.2.11";
|
|
break;
|
|
default:
|
|
}
|
|
break;
|
|
case "DH":
|
|
result = "1.2.840.113549.1.9.16.3.5";
|
|
break;
|
|
case "SHA-1":
|
|
result = "1.3.14.3.2.26";
|
|
break;
|
|
case "SHA-256":
|
|
result = "2.16.840.1.101.3.4.2.1";
|
|
break;
|
|
case "SHA-384":
|
|
result = "2.16.840.1.101.3.4.2.2";
|
|
break;
|
|
case "SHA-512":
|
|
result = "2.16.840.1.101.3.4.2.3";
|
|
break;
|
|
case "CONCAT":
|
|
break;
|
|
case "HKDF":
|
|
break;
|
|
case "PBKDF2":
|
|
result = "1.2.840.113549.1.5.12";
|
|
break;
|
|
//region Special case - OIDs for ECC curves
|
|
case "P-256":
|
|
result = "1.2.840.10045.3.1.7";
|
|
break;
|
|
case "P-384":
|
|
result = "1.3.132.0.34";
|
|
break;
|
|
case "P-521":
|
|
result = "1.3.132.0.35";
|
|
break;
|
|
//endregion
|
|
default:
|
|
}
|
|
|
|
return result;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* 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 {*}
|
|
*/
|
|
getAlgorithmParameters(algorithmName, operation)
|
|
{
|
|
let result = {
|
|
algorithm: {},
|
|
usages: []
|
|
};
|
|
|
|
switch(algorithmName.toUpperCase())
|
|
{
|
|
case "RSASSA-PKCS1-V1_5":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
},
|
|
usages: ["sign", "verify"]
|
|
};
|
|
break;
|
|
case "verify":
|
|
case "sign":
|
|
case "importkey":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSASSA-PKCS1-v1_5",
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
},
|
|
usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
|
|
};
|
|
break;
|
|
case "exportkey":
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "RSASSA-PKCS1-v1_5"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "RSA-PSS":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "sign":
|
|
case "verify":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSA-PSS",
|
|
hash: {
|
|
name: "SHA-1"
|
|
},
|
|
saltLength: 20
|
|
},
|
|
usages: ["sign", "verify"]
|
|
};
|
|
break;
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSA-PSS",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
hash: {
|
|
name: "SHA-1"
|
|
}
|
|
},
|
|
usages: ["sign", "verify"]
|
|
};
|
|
break;
|
|
case "importkey":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSA-PSS",
|
|
hash: {
|
|
name: "SHA-1"
|
|
}
|
|
},
|
|
usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
|
|
};
|
|
break;
|
|
case "exportkey":
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "RSA-PSS"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "RSA-OAEP":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "encrypt":
|
|
case "decrypt":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSA-OAEP"
|
|
},
|
|
usages: ["encrypt", "decrypt"]
|
|
};
|
|
break;
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSA-OAEP",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
case "importkey":
|
|
result = {
|
|
algorithm: {
|
|
name: "RSA-OAEP",
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
},
|
|
usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8"
|
|
};
|
|
break;
|
|
case "exportkey":
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "RSA-OAEP"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "ECDSA":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "ECDSA",
|
|
namedCurve: "P-256"
|
|
},
|
|
usages: ["sign", "verify"]
|
|
};
|
|
break;
|
|
case "importkey":
|
|
result = {
|
|
algorithm: {
|
|
name: "ECDSA",
|
|
namedCurve: "P-256"
|
|
},
|
|
usages: ["verify"] // "sign" for "pkcs8"
|
|
};
|
|
break;
|
|
case "verify":
|
|
case "sign":
|
|
result = {
|
|
algorithm: {
|
|
name: "ECDSA",
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
},
|
|
usages: ["sign"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "ECDSA"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "ECDH":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "exportkey":
|
|
case "importkey":
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "ECDH",
|
|
namedCurve: "P-256"
|
|
},
|
|
usages: ["deriveKey", "deriveBits"]
|
|
};
|
|
break;
|
|
case "derivekey":
|
|
case "derivebits":
|
|
result = {
|
|
algorithm: {
|
|
name: "ECDH",
|
|
namedCurve: "P-256",
|
|
public: [] // Must be a "publicKey"
|
|
},
|
|
usages: ["encrypt", "decrypt"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "ECDH"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "AES-CTR":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "importkey":
|
|
case "exportkey":
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-CTR",
|
|
length: 256
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
case "decrypt":
|
|
case "encrypt":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-CTR",
|
|
counter: new Uint8Array(16),
|
|
length: 10
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "AES-CTR"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "AES-CBC":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "importkey":
|
|
case "exportkey":
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-CBC",
|
|
length: 256
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
case "decrypt":
|
|
case "encrypt":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-CBC",
|
|
iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "AES-CBC"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "AES-GCM":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "importkey":
|
|
case "exportkey":
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-GCM",
|
|
length: 256
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
case "decrypt":
|
|
case "encrypt":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-GCM",
|
|
iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
|
|
},
|
|
usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "AES-GCM"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "AES-KW":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "importkey":
|
|
case "exportkey":
|
|
case "generatekey":
|
|
case "wrapkey":
|
|
case "unwrapkey":
|
|
result = {
|
|
algorithm: {
|
|
name: "AES-KW",
|
|
length: 256
|
|
},
|
|
usages: ["wrapKey", "unwrapKey"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "AES-KW"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "HMAC":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "sign":
|
|
case "verify":
|
|
result = {
|
|
algorithm: {
|
|
name: "HMAC"
|
|
},
|
|
usages: ["sign", "verify"]
|
|
};
|
|
break;
|
|
case "importkey":
|
|
case "exportkey":
|
|
case "generatekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "HMAC",
|
|
length: 32,
|
|
hash: {
|
|
name: "SHA-256"
|
|
}
|
|
},
|
|
usages: ["sign", "verify"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "HMAC"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "HKDF":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "derivekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "HKDF",
|
|
hash: "SHA-256",
|
|
salt: new Uint8Array([]),
|
|
info: new Uint8Array([])
|
|
},
|
|
usages: ["encrypt", "decrypt"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "HKDF"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
case "PBKDF2":
|
|
switch(operation.toLowerCase())
|
|
{
|
|
case "derivekey":
|
|
result = {
|
|
algorithm: {
|
|
name: "PBKDF2",
|
|
hash: { name: "SHA-256" },
|
|
salt: new Uint8Array([]),
|
|
iterations: 10000
|
|
},
|
|
usages: ["encrypt", "decrypt"]
|
|
};
|
|
break;
|
|
default:
|
|
return {
|
|
algorithm: {
|
|
name: "PBKDF2"
|
|
},
|
|
usages: []
|
|
};
|
|
}
|
|
break;
|
|
default:
|
|
}
|
|
|
|
return result;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Getting hash algorithm by signature algorithm
|
|
* @param {AlgorithmIdentifier} signatureAlgorithm Signature algorithm
|
|
* @returns {string}
|
|
*/
|
|
getHashAlgorithm(signatureAlgorithm)
|
|
{
|
|
let result = "";
|
|
|
|
switch(signatureAlgorithm.algorithmId)
|
|
{
|
|
case "1.2.840.10045.4.1": // ecdsa-with-SHA1
|
|
case "1.2.840.113549.1.1.5":
|
|
result = "SHA-1";
|
|
break;
|
|
case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256
|
|
case "1.2.840.113549.1.1.11":
|
|
result = "SHA-256";
|
|
break;
|
|
case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384
|
|
case "1.2.840.113549.1.1.12":
|
|
result = "SHA-384";
|
|
break;
|
|
case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512
|
|
case "1.2.840.113549.1.1.13":
|
|
result = "SHA-512";
|
|
break;
|
|
case "1.2.840.113549.1.1.10": // RSA-PSS
|
|
{
|
|
try
|
|
{
|
|
const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
|
|
if("hashAlgorithm" in params)
|
|
{
|
|
const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
|
|
if(("name" in algorithm) === false)
|
|
return "";
|
|
|
|
result = algorithm.name;
|
|
}
|
|
else
|
|
result = "SHA-1";
|
|
}
|
|
catch(ex)
|
|
{
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
}
|
|
|
|
return result;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Specialized function encrypting "EncryptedContentInfo" object using parameters
|
|
* @param {Object} parameters
|
|
* @returns {Promise}
|
|
*/
|
|
encryptEncryptedContentInfo(parameters)
|
|
{
|
|
//region Check for input parameters
|
|
if((parameters instanceof Object) === false)
|
|
return Promise.reject("Parameters must have type \"Object\"");
|
|
|
|
if(("password" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"password\"");
|
|
|
|
if(("contentEncryptionAlgorithm" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"contentEncryptionAlgorithm\"");
|
|
|
|
if(("hmacHashAlgorithm" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"hmacHashAlgorithm\"");
|
|
|
|
if(("iterationCount" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"iterationCount\"");
|
|
|
|
if(("contentToEncrypt" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"contentToEncrypt\"");
|
|
|
|
if(("contentType" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"contentType\"");
|
|
|
|
const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm);
|
|
if(contentEncryptionOID === "")
|
|
return Promise.reject("Wrong \"contentEncryptionAlgorithm\" value");
|
|
|
|
const pbkdf2OID = this.getOIDByAlgorithm({
|
|
name: "PBKDF2"
|
|
});
|
|
if(pbkdf2OID === "")
|
|
return Promise.reject("Can not find OID for PBKDF2");
|
|
|
|
const hmacOID = this.getOIDByAlgorithm({
|
|
name: "HMAC",
|
|
hash: {
|
|
name: parameters.hmacHashAlgorithm
|
|
}
|
|
});
|
|
if(hmacOID === "")
|
|
return Promise.reject(`Incorrect value for "hmacHashAlgorithm": ${parameters.hmacHashAlgorithm}`);
|
|
//endregion
|
|
|
|
//region Initial variables
|
|
let sequence = Promise.resolve();
|
|
|
|
const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long
|
|
const ivView = new Uint8Array(ivBuffer);
|
|
this.getRandomValues(ivView);
|
|
|
|
const saltBuffer = new ArrayBuffer(64);
|
|
const saltView = new Uint8Array(saltBuffer);
|
|
this.getRandomValues(saltView);
|
|
|
|
const contentView = new Uint8Array(parameters.contentToEncrypt);
|
|
|
|
const pbkdf2Params = new PBKDF2Params({
|
|
salt: new asn1js.OctetString({ valueHex: saltBuffer }),
|
|
iterationCount: parameters.iterationCount,
|
|
prf: new AlgorithmIdentifier({
|
|
algorithmId: hmacOID,
|
|
algorithmParams: new asn1js.Null()
|
|
})
|
|
});
|
|
//endregion
|
|
|
|
//region Derive PBKDF2 key from "password" buffer
|
|
sequence = sequence.then(() =>
|
|
{
|
|
const passwordView = new Uint8Array(parameters.password);
|
|
|
|
return this.importKey("raw",
|
|
passwordView,
|
|
"PBKDF2",
|
|
false,
|
|
["deriveKey"]);
|
|
}, error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
//region Derive key for "contentEncryptionAlgorithm"
|
|
sequence = sequence.then(result =>
|
|
this.deriveKey({
|
|
name: "PBKDF2",
|
|
hash: {
|
|
name: parameters.hmacHashAlgorithm
|
|
},
|
|
salt: saltView,
|
|
iterations: parameters.iterationCount
|
|
},
|
|
result,
|
|
parameters.contentEncryptionAlgorithm,
|
|
false,
|
|
["encrypt"]),
|
|
error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
//region Encrypt content
|
|
sequence = sequence.then(result =>
|
|
this.encrypt({
|
|
name: parameters.contentEncryptionAlgorithm.name,
|
|
iv: ivView
|
|
},
|
|
result,
|
|
contentView),
|
|
error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
//region Store all parameters in EncryptedData object
|
|
sequence = sequence.then(result =>
|
|
{
|
|
const pbes2Parameters = new PBES2Params({
|
|
keyDerivationFunc: new AlgorithmIdentifier({
|
|
algorithmId: pbkdf2OID,
|
|
algorithmParams: pbkdf2Params.toSchema()
|
|
}),
|
|
encryptionScheme: new AlgorithmIdentifier({
|
|
algorithmId: contentEncryptionOID,
|
|
algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
|
|
})
|
|
});
|
|
|
|
return new EncryptedContentInfo({
|
|
contentType: parameters.contentType,
|
|
contentEncryptionAlgorithm: new AlgorithmIdentifier({
|
|
algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2
|
|
algorithmParams: pbes2Parameters.toSchema()
|
|
}),
|
|
encryptedContent: new asn1js.OctetString({ valueHex: result })
|
|
});
|
|
}, error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
return sequence;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Decrypt data stored in "EncryptedContentInfo" object using parameters
|
|
* @param parameters
|
|
* @return {Promise}
|
|
*/
|
|
decryptEncryptedContentInfo(parameters)
|
|
{
|
|
//region Check for input parameters
|
|
if((parameters instanceof Object) === false)
|
|
return Promise.reject("Parameters must have type \"Object\"");
|
|
|
|
if(("password" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"password\"");
|
|
|
|
if(("encryptedContentInfo" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"encryptedContentInfo\"");
|
|
|
|
if(parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2
|
|
return Promise.reject(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
|
|
//endregion
|
|
|
|
//region Initial variables
|
|
let sequence = Promise.resolve();
|
|
|
|
let pbes2Parameters;
|
|
|
|
try
|
|
{
|
|
pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams });
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject("Incorrectly encoded \"pbes2Parameters\"");
|
|
}
|
|
|
|
let pbkdf2Params;
|
|
|
|
try
|
|
{
|
|
pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject("Incorrectly encoded \"pbkdf2Params\"");
|
|
}
|
|
|
|
const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId);
|
|
if(("name" in contentEncryptionAlgorithm) === false)
|
|
return Promise.reject(`Incorrect OID for "contentEncryptionAlgorithm": ${pbes2Parameters.encryptionScheme.algorithmId}`);
|
|
|
|
const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex;
|
|
const ivView = new Uint8Array(ivBuffer);
|
|
|
|
const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex;
|
|
const saltView = new Uint8Array(saltBuffer);
|
|
|
|
const iterationCount = pbkdf2Params.iterationCount;
|
|
|
|
let hmacHashAlgorithm = "SHA-1";
|
|
|
|
if("prf" in pbkdf2Params)
|
|
{
|
|
const algorithm = this.getAlgorithmByOID(pbkdf2Params.prf.algorithmId);
|
|
if(("name" in algorithm) === false)
|
|
return Promise.reject("Incorrect OID for HMAC hash algorithm");
|
|
|
|
hmacHashAlgorithm = algorithm.hash.name;
|
|
}
|
|
//endregion
|
|
|
|
//region Derive PBKDF2 key from "password" buffer
|
|
sequence = sequence.then(() =>
|
|
this.importKey("raw",
|
|
parameters.password,
|
|
"PBKDF2",
|
|
false,
|
|
["deriveKey"]),
|
|
error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
//region Derive key for "contentEncryptionAlgorithm"
|
|
sequence = sequence.then(result =>
|
|
this.deriveKey({
|
|
name: "PBKDF2",
|
|
hash: {
|
|
name: hmacHashAlgorithm
|
|
},
|
|
salt: saltView,
|
|
iterations: iterationCount
|
|
},
|
|
result,
|
|
contentEncryptionAlgorithm,
|
|
false,
|
|
["decrypt"]),
|
|
error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
//region Decrypt internal content using derived key
|
|
sequence = sequence.then(result =>
|
|
{
|
|
//region Create correct data block for decryption
|
|
let dataBuffer = new ArrayBuffer(0);
|
|
|
|
if(parameters.encryptedContentInfo.encryptedContent.idBlock.isConstructed === false)
|
|
dataBuffer = parameters.encryptedContentInfo.encryptedContent.valueBlock.valueHex;
|
|
else
|
|
{
|
|
for(const content of parameters.encryptedContentInfo.encryptedContent.valueBlock.value)
|
|
dataBuffer = utilConcatBuf(dataBuffer, content.valueBlock.valueHex);
|
|
}
|
|
//endregion
|
|
|
|
return this.decrypt({
|
|
name: contentEncryptionAlgorithm.name,
|
|
iv: ivView
|
|
},
|
|
result,
|
|
dataBuffer);
|
|
}, error =>
|
|
Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
return sequence;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Stamping (signing) data using algorithm simular to HMAC
|
|
* @param {Object} parameters
|
|
* @return {Promise.<T>|Promise}
|
|
*/
|
|
stampDataWithPassword(parameters)
|
|
{
|
|
//region Check for input parameters
|
|
if((parameters instanceof Object) === false)
|
|
return Promise.reject("Parameters must have type \"Object\"");
|
|
|
|
if(("password" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"password\"");
|
|
|
|
if(("hashAlgorithm" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
|
|
|
|
if(("salt" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"iterationCount\"");
|
|
|
|
if(("iterationCount" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"salt\"");
|
|
|
|
if(("contentToStamp" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"contentToStamp\"");
|
|
//endregion
|
|
|
|
//region Choose correct length for HMAC key
|
|
let length;
|
|
|
|
switch(parameters.hashAlgorithm.toLowerCase())
|
|
{
|
|
case "sha-1":
|
|
length = 160;
|
|
break;
|
|
case "sha-256":
|
|
length = 256;
|
|
break;
|
|
case "sha-384":
|
|
length = 384;
|
|
break;
|
|
case "sha-512":
|
|
length = 512;
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
|
|
}
|
|
//endregion
|
|
|
|
//region Initial variables
|
|
let sequence = Promise.resolve();
|
|
|
|
const hmacAlgorithm = {
|
|
name: "HMAC",
|
|
length,
|
|
hash: {
|
|
name: parameters.hashAlgorithm
|
|
}
|
|
};
|
|
//endregion
|
|
|
|
//region Create PKCS#12 key for integrity checking
|
|
sequence = sequence.then(() => makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount));
|
|
//endregion
|
|
|
|
//region Import HMAC key
|
|
// noinspection JSCheckFunctionSignatures
|
|
sequence = sequence.then(
|
|
result =>
|
|
this.importKey("raw",
|
|
new Uint8Array(result),
|
|
hmacAlgorithm,
|
|
false,
|
|
["sign"])
|
|
);
|
|
//endregion
|
|
|
|
//region Make signed HMAC value
|
|
sequence = sequence.then(
|
|
result =>
|
|
this.sign(hmacAlgorithm, result, new Uint8Array(parameters.contentToStamp)),
|
|
error => Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
return sequence;
|
|
}
|
|
//**********************************************************************************
|
|
verifyDataStampedWithPassword(parameters)
|
|
{
|
|
//region Check for input parameters
|
|
if((parameters instanceof Object) === false)
|
|
return Promise.reject("Parameters must have type \"Object\"");
|
|
|
|
if(("password" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"password\"");
|
|
|
|
if(("hashAlgorithm" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
|
|
|
|
if(("salt" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"iterationCount\"");
|
|
|
|
if(("iterationCount" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"salt\"");
|
|
|
|
if(("contentToVerify" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"contentToVerify\"");
|
|
|
|
if(("signatureToVerify" in parameters) === false)
|
|
return Promise.reject("Absent mandatory parameter \"signatureToVerify\"");
|
|
//endregion
|
|
|
|
//region Choose correct length for HMAC key
|
|
let length;
|
|
|
|
switch(parameters.hashAlgorithm.toLowerCase())
|
|
{
|
|
case "sha-1":
|
|
length = 160;
|
|
break;
|
|
case "sha-256":
|
|
length = 256;
|
|
break;
|
|
case "sha-384":
|
|
length = 384;
|
|
break;
|
|
case "sha-512":
|
|
length = 512;
|
|
break;
|
|
default:
|
|
return Promise.reject(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
|
|
}
|
|
//endregion
|
|
|
|
//region Initial variables
|
|
let sequence = Promise.resolve();
|
|
|
|
const hmacAlgorithm = {
|
|
name: "HMAC",
|
|
length,
|
|
hash: {
|
|
name: parameters.hashAlgorithm
|
|
}
|
|
};
|
|
//endregion
|
|
|
|
//region Create PKCS#12 key for integrity checking
|
|
sequence = sequence.then(() => makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount));
|
|
//endregion
|
|
|
|
//region Import HMAC key
|
|
// noinspection JSCheckFunctionSignatures
|
|
sequence = sequence.then(result =>
|
|
this.importKey("raw",
|
|
new Uint8Array(result),
|
|
hmacAlgorithm,
|
|
false,
|
|
["verify"])
|
|
);
|
|
//endregion
|
|
|
|
//region Make signed HMAC value
|
|
sequence = sequence.then(
|
|
result =>
|
|
this.verify(hmacAlgorithm, result, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)),
|
|
error => Promise.reject(error)
|
|
);
|
|
//endregion
|
|
|
|
return sequence;
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Get signature parameters by analyzing private key algorithm
|
|
* @param {Object} privateKey The private key user would like to use
|
|
* @param {string} [hashAlgorithm="SHA-1"] Hash algorithm user would like to use
|
|
* @return {Promise.<T>|Promise}
|
|
*/
|
|
getSignatureParameters(privateKey, hashAlgorithm = "SHA-1")
|
|
{
|
|
//region Check hashing algorithm
|
|
const oid = this.getOIDByAlgorithm({ name: hashAlgorithm });
|
|
if(oid === "")
|
|
return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`);
|
|
//endregion
|
|
|
|
//region Initial variables
|
|
const signatureAlgorithm = new AlgorithmIdentifier();
|
|
//endregion
|
|
|
|
//region Get a "default parameters" for current algorithm
|
|
const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
|
|
parameters.algorithm.hash.name = hashAlgorithm;
|
|
//endregion
|
|
|
|
//region Fill internal structures base on "privateKey" and "hashAlgorithm"
|
|
switch(privateKey.algorithm.name.toUpperCase())
|
|
{
|
|
case "RSASSA-PKCS1-V1_5":
|
|
case "ECDSA":
|
|
signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(parameters.algorithm);
|
|
break;
|
|
case "RSA-PSS":
|
|
{
|
|
//region Set "saltLength" as a length (in octets) of hash function result
|
|
switch(hashAlgorithm.toUpperCase())
|
|
{
|
|
case "SHA-256":
|
|
parameters.algorithm.saltLength = 32;
|
|
break;
|
|
case "SHA-384":
|
|
parameters.algorithm.saltLength = 48;
|
|
break;
|
|
case "SHA-512":
|
|
parameters.algorithm.saltLength = 64;
|
|
break;
|
|
default:
|
|
}
|
|
//endregion
|
|
|
|
//region Fill "RSASSA_PSS_params" object
|
|
const paramsObject = {};
|
|
|
|
if(hashAlgorithm.toUpperCase() !== "SHA-1")
|
|
{
|
|
const hashAlgorithmOID = this.getOIDByAlgorithm({ name: hashAlgorithm });
|
|
if(hashAlgorithmOID === "")
|
|
return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`);
|
|
|
|
paramsObject.hashAlgorithm = new AlgorithmIdentifier({
|
|
algorithmId: hashAlgorithmOID,
|
|
algorithmParams: new asn1js.Null()
|
|
});
|
|
|
|
paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({
|
|
algorithmId: "1.2.840.113549.1.1.8", // MGF1
|
|
algorithmParams: paramsObject.hashAlgorithm.toSchema()
|
|
});
|
|
}
|
|
|
|
if(parameters.algorithm.saltLength !== 20)
|
|
paramsObject.saltLength = parameters.algorithm.saltLength;
|
|
|
|
const pssParameters = new RSASSAPSSParams(paramsObject);
|
|
//endregion
|
|
|
|
//region Automatically set signature algorithm
|
|
signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10";
|
|
signatureAlgorithm.algorithmParams = pssParameters.toSchema();
|
|
//endregion
|
|
}
|
|
break;
|
|
default:
|
|
return Promise.reject(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
|
|
}
|
|
//endregion
|
|
|
|
return Promise.resolve().then(() => ({
|
|
signatureAlgorithm,
|
|
parameters
|
|
}));
|
|
}
|
|
//**********************************************************************************
|
|
/**
|
|
* Sign data with pre-defined private key
|
|
* @param {ArrayBuffer} data Data to be signed
|
|
* @param {Object} privateKey Private key to use
|
|
* @param {Object} parameters Parameters for used algorithm
|
|
* @return {Promise.<T>|Promise}
|
|
*/
|
|
signWithPrivateKey(data, privateKey, parameters)
|
|
{
|
|
return this.sign(parameters.algorithm,
|
|
privateKey,
|
|
new Uint8Array(data))
|
|
.then(result =>
|
|
{
|
|
//region Special case for ECDSA algorithm
|
|
if(parameters.algorithm.name === "ECDSA")
|
|
result = createCMSECDSASignature(result);
|
|
//endregion
|
|
|
|
return result;
|
|
}, error =>
|
|
Promise.reject(`Signing error: ${error}`)
|
|
);
|
|
}
|
|
//**********************************************************************************
|
|
fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm)
|
|
{
|
|
const parameters = {};
|
|
|
|
//region Find signer's hashing algorithm
|
|
const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
|
|
if(shaAlgorithm === "")
|
|
return Promise.reject(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
|
|
//endregion
|
|
|
|
//region Get information about public key algorithm and default parameters for import
|
|
let algorithmId;
|
|
if(signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
|
|
algorithmId = signatureAlgorithm.algorithmId;
|
|
else
|
|
algorithmId = publicKeyInfo.algorithm.algorithmId;
|
|
|
|
const algorithmObject = this.getAlgorithmByOID(algorithmId);
|
|
if(("name" in algorithmObject) === "")
|
|
return Promise.reject(`Unsupported public key algorithm: ${signatureAlgorithm.algorithmId}`);
|
|
|
|
parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importkey");
|
|
if("hash" in parameters.algorithm.algorithm)
|
|
parameters.algorithm.algorithm.hash.name = shaAlgorithm;
|
|
|
|
//region Special case for ECDSA
|
|
if(algorithmObject.name === "ECDSA")
|
|
{
|
|
//region Get information about named curve
|
|
let algorithmParamsChecked = false;
|
|
|
|
if(("algorithmParams" in publicKeyInfo.algorithm) === true)
|
|
{
|
|
if("idBlock" in publicKeyInfo.algorithm.algorithmParams)
|
|
{
|
|
if((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
|
|
algorithmParamsChecked = true;
|
|
}
|
|
}
|
|
|
|
if(algorithmParamsChecked === false)
|
|
return Promise.reject("Incorrect type for ECDSA public key parameters");
|
|
|
|
const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString());
|
|
if(("name" in curveObject) === false)
|
|
return Promise.reject(`Unsupported named curve algorithm: ${publicKeyInfo.algorithm.algorithmParams.valueBlock.toString()}`);
|
|
//endregion
|
|
|
|
parameters.algorithm.algorithm.namedCurve = curveObject.name;
|
|
}
|
|
//endregion
|
|
//endregion
|
|
|
|
return parameters;
|
|
}
|
|
//**********************************************************************************
|
|
getPublicKey(publicKeyInfo, signatureAlgorithm, parameters = null)
|
|
{
|
|
if(parameters === null)
|
|
parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
|
|
|
|
const publicKeyInfoSchema = publicKeyInfo.toSchema();
|
|
const publicKeyInfoBuffer = publicKeyInfoSchema.toBER(false);
|
|
const publicKeyInfoView = new Uint8Array(publicKeyInfoBuffer);
|
|
|
|
return this.importKey("spki",
|
|
publicKeyInfoView,
|
|
parameters.algorithm.algorithm,
|
|
true,
|
|
parameters.algorithm.usages
|
|
);
|
|
}
|
|
//**********************************************************************************
|
|
verifyWithPublicKey(data, signature, publicKeyInfo, signatureAlgorithm, shaAlgorithm = null)
|
|
{
|
|
//region Initial variables
|
|
let sequence = Promise.resolve();
|
|
//endregion
|
|
|
|
//region Find signer's hashing algorithm
|
|
if(shaAlgorithm === null)
|
|
{
|
|
shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
|
|
if(shaAlgorithm === "")
|
|
return Promise.reject(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
|
|
|
|
//region Import public key
|
|
sequence = sequence.then(() =>
|
|
this.getPublicKey(publicKeyInfo, signatureAlgorithm));
|
|
//endregion
|
|
}
|
|
else
|
|
{
|
|
const parameters = {};
|
|
|
|
//region Get information about public key algorithm and default parameters for import
|
|
let algorithmId;
|
|
if(signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
|
|
algorithmId = signatureAlgorithm.algorithmId;
|
|
else
|
|
algorithmId = publicKeyInfo.algorithm.algorithmId;
|
|
|
|
const algorithmObject = this.getAlgorithmByOID(algorithmId);
|
|
if(("name" in algorithmObject) === "")
|
|
return Promise.reject(`Unsupported public key algorithm: ${signatureAlgorithm.algorithmId}`);
|
|
|
|
parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importkey");
|
|
if("hash" in parameters.algorithm.algorithm)
|
|
parameters.algorithm.algorithm.hash.name = shaAlgorithm;
|
|
|
|
//region Special case for ECDSA
|
|
if(algorithmObject.name === "ECDSA")
|
|
{
|
|
//region Get information about named curve
|
|
let algorithmParamsChecked = false;
|
|
|
|
if(("algorithmParams" in publicKeyInfo.algorithm) === true)
|
|
{
|
|
if("idBlock" in publicKeyInfo.algorithm.algorithmParams)
|
|
{
|
|
if((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
|
|
algorithmParamsChecked = true;
|
|
}
|
|
}
|
|
|
|
if(algorithmParamsChecked === false)
|
|
return Promise.reject("Incorrect type for ECDSA public key parameters");
|
|
|
|
const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString());
|
|
if(("name" in curveObject) === false)
|
|
return Promise.reject(`Unsupported named curve algorithm: ${publicKeyInfo.algorithm.algorithmParams.valueBlock.toString()}`);
|
|
//endregion
|
|
|
|
parameters.algorithm.algorithm.namedCurve = curveObject.name;
|
|
}
|
|
//endregion
|
|
//endregion
|
|
|
|
//region Import public key
|
|
sequence = sequence.then(() =>
|
|
this.getPublicKey(publicKeyInfo, null, parameters));
|
|
//endregion
|
|
}
|
|
//endregion
|
|
|
|
//region Verify signature
|
|
sequence = sequence.then(publicKey =>
|
|
{
|
|
//region Get default algorithm parameters for verification
|
|
const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
|
|
if("hash" in algorithm.algorithm)
|
|
algorithm.algorithm.hash.name = shaAlgorithm;
|
|
//endregion
|
|
|
|
//region Special case for ECDSA signatures
|
|
let signatureValue = signature.valueBlock.valueHex;
|
|
|
|
if(publicKey.algorithm.name === "ECDSA")
|
|
{
|
|
const asn1 = asn1js.fromBER(signatureValue);
|
|
// noinspection JSCheckFunctionSignatures
|
|
signatureValue = createECDSASignatureFromCMS(asn1.result);
|
|
}
|
|
//endregion
|
|
|
|
//region Special case for RSA-PSS
|
|
if(publicKey.algorithm.name === "RSA-PSS")
|
|
{
|
|
let pssParameters;
|
|
|
|
try
|
|
{
|
|
pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
|
|
}
|
|
catch(ex)
|
|
{
|
|
return Promise.reject(ex);
|
|
}
|
|
|
|
if("saltLength" in pssParameters)
|
|
algorithm.algorithm.saltLength = pssParameters.saltLength;
|
|
else
|
|
algorithm.algorithm.saltLength = 20;
|
|
|
|
let hashAlgo = "SHA-1";
|
|
|
|
if("hashAlgorithm" in pssParameters)
|
|
{
|
|
const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId);
|
|
if(("name" in hashAlgorithm) === false)
|
|
return Promise.reject(`Unrecognized hash algorithm: ${pssParameters.hashAlgorithm.algorithmId}`);
|
|
|
|
hashAlgo = hashAlgorithm.name;
|
|
}
|
|
|
|
algorithm.algorithm.hash.name = hashAlgo;
|
|
}
|
|
//endregion
|
|
|
|
return this.verify(algorithm.algorithm,
|
|
publicKey,
|
|
new Uint8Array(signatureValue),
|
|
new Uint8Array(data)
|
|
);
|
|
});
|
|
//endregion
|
|
|
|
return sequence;
|
|
}
|
|
//**********************************************************************************
|
|
}
|
|
//**************************************************************************************
|