Source code

Revision control

Copy as Markdown

Other Tools

import * as asn1js from "asn1js";
import * as pvutils from "pvutils";
import * as pvtsutils from "pvtsutils";
import * as common from "../common";
import { PublicKeyInfo } from "../PublicKeyInfo";
import { PrivateKeyInfo } from "../PrivateKeyInfo";
import { AlgorithmIdentifier } from "../AlgorithmIdentifier";
import { EncryptedContentInfo } from "../EncryptedContentInfo";
import { IRSASSAPSSParams, RSASSAPSSParams } from "../RSASSAPSSParams";
import { PBKDF2Params } from "../PBKDF2Params";
import { PBES2Params } from "../PBES2Params";
import { ArgumentError, AsnError, ParameterError } from "../errors";
import * as type from "./CryptoEngineInterface";
import { AbstractCryptoEngine } from "./AbstractCryptoEngine";
import { EMPTY_STRING } from "../constants";
import { ECNamedCurves } from "../ECNamedCurves";
/**
* Making MAC key using algorithm described in B.2 of PKCS#12 standard.
*/
async function makePKCS12B2Key(cryptoEngine: CryptoEngine, hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) {
//#region Initial variables
let u: number;
let v: number;
const result: number[] = [];
//#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 as any)[j % roundBuffer.byteLength]; // TODO roundBuffer is ArrayBuffer. It doesn't have indexed values
//#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;
}
function prepareAlgorithm(data: globalThis.AlgorithmIdentifier | EcdsaParams): Algorithm & { hash?: Algorithm; } {
const res = typeof data === "string"
? { name: data }
: data;
// TODO fix type casting `as EcdsaParams`
if ("hash" in (res as EcdsaParams)) {
return {
...res,
hash: prepareAlgorithm((res as EcdsaParams).hash)
};
}
return res;
}
/**
* Default cryptographic engine for Web Cryptography API
*/
export class CryptoEngine extends AbstractCryptoEngine {
public override async importKey(format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey> {
//#region Initial variables
let jwk: JsonWebKey = {};
//#endregion
const alg = prepareAlgorithm(algorithm);
switch (format.toLowerCase()) {
case "raw":
return this.subtle.importKey("raw", keyData as BufferSource, algorithm, extractable, keyUsages);
case "spki":
{
const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource));
AsnError.assert(asn1, "keyData");
const publicKeyInfo = new PublicKeyInfo();
try {
publicKeyInfo.fromSchema(asn1.result);
} catch {
throw new ArgumentError("Incorrect keyData");
}
switch (alg.name.toUpperCase()) {
case "RSA-PSS":
{
//#region Get information about used hash function
if (!alg.hash) {
throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
}
switch (alg.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:
throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
}
//#endregion
}
// break omitted
// eslint-disable-next-line no-fallthrough
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")
throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
//#region Get information about used hash function
if (!jwk.alg) {
if (!alg.hash) {
throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
}
switch (alg.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:
throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
}
}
//#endregion
//#region Create RSA Public Key elements
const publicKeyJSON = publicKeyInfo.toJSON();
Object.assign(jwk, publicKeyJSON);
//#endregion
}
break;
case "ECDSA":
keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
// break omitted
// eslint-disable-next-line no-fallthrough
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") {
throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
}
//#endregion
//#region Create ECDSA Public Key elements
const publicKeyJSON = publicKeyInfo.toJSON();
Object.assign(jwk, publicKeyJSON);
//#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 {
if (!alg.hash) {
throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
}
switch (alg.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:
throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
}
}
//#region Create ECDSA Public Key elements
const publicKeyJSON = publicKeyInfo.toJSON();
Object.assign(jwk, publicKeyJSON);
//#endregion
}
break;
case "RSAES-PKCS1-V1_5":
{
jwk.kty = "RSA";
jwk.ext = extractable;
jwk.key_ops = keyUsages;
jwk.alg = "PS1";
const publicKeyJSON = publicKeyInfo.toJSON();
Object.assign(jwk, publicKeyJSON);
}
break;
default:
throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
}
}
break;
case "pkcs8":
{
const privateKeyInfo = new PrivateKeyInfo();
//#region Parse "PrivateKeyInfo" object
const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource));
AsnError.assert(asn1, "keyData");
try {
privateKeyInfo.fromSchema(asn1.result);
}
catch (ex) {
throw new Error("Incorrect keyData");
}
if (!privateKeyInfo.parsedKey)
throw new Error("Incorrect keyData");
//#endregion
switch (alg.name.toUpperCase()) {
case "RSA-PSS":
{
//#region Get information about used hash function
switch (alg.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:
throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
}
//#endregion
}
// break omitted
// eslint-disable-next-line no-fallthrough
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")
throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
//#endregion
//#region Get information about used hash function
if (("alg" in jwk) === false) {
switch (alg.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:
throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
}
}
//#endregion
//#region Create RSA Private Key elements
const privateKeyJSON = privateKeyInfo.toJSON();
Object.assign(jwk, privateKeyJSON);
//#endregion
}
break;
case "ECDSA":
keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
// break omitted
// eslint-disable-next-line no-fallthrough
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")
throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
//#endregion
//#region Create ECDSA Private Key elements
const privateKeyJSON = privateKeyInfo.toJSON();
Object.assign(jwk, privateKeyJSON);
//#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 (alg.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:
throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
}
}
//#endregion
//#region Create RSA Private Key elements
const privateKeyJSON = privateKeyInfo.toJSON();
Object.assign(jwk, privateKeyJSON);
//#endregion
}
break;
case "RSAES-PKCS1-V1_5":
{
keyUsages = ["decrypt"]; // Override existing keyUsages value since the key is a private key
jwk.kty = "RSA";
jwk.ext = extractable;
jwk.key_ops = keyUsages;
jwk.alg = "PS1";
//#region Create RSA Private Key elements
const privateKeyJSON = privateKeyInfo.toJSON();
Object.assign(jwk, privateKeyJSON);
//#endregion
}
break;
default:
throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
}
}
break;
case "jwk":
jwk = keyData as JsonWebKey;
break;
default:
throw new Error(`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)
try {
return this.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages);
} catch {
return 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 format
* @param key
*/
public override exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>;
public override exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>;
public override exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;
public override async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey> {
let jwk = await this.subtle.exportKey("jwk", key);
//#region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation
if (this.name.toLowerCase() === "safari") {
// Some additional checks for Safari Technology Preview
if (jwk instanceof ArrayBuffer) {
jwk = JSON.parse(pvutils.arrayBufferToString(jwk));
}
}
//#endregion
switch (format.toLowerCase()) {
case "raw":
return this.subtle.exportKey("raw", key);
case "spki": {
const publicKeyInfo = new PublicKeyInfo();
try {
publicKeyInfo.fromJSON(jwk);
}
catch (ex) {
throw new Error("Incorrect key data");
}
return publicKeyInfo.toSchema().toBER(false);
}
case "pkcs8": {
const privateKeyInfo = new PrivateKeyInfo();
try {
privateKeyInfo.fromJSON(jwk);
}
catch (ex) {
throw new Error("Incorrect key data");
}
return privateKeyInfo.toSchema().toBER(false);
}
case "jwk":
return jwk;
default:
throw new Error(`Incorrect format: ${format}`);
}
}
/**
* Convert WebCrypto keys between different export formats
* @param inputFormat
* @param outputFormat
* @param keyData
* @param algorithm
* @param extractable
* @param keyUsages
*/
public async convert(inputFormat: KeyFormat, outputFormat: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]) {
if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) {
return keyData;
}
const key = await this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages);
return this.exportKey(outputFormat, key);
}
/**
* Gets WebCrypto algorithm by wel-known OID
* @param oid algorithm identifier
* @param safety if `true` throws exception on unknown algorithm identifier
* @param target name of the target
* @returns Returns WebCrypto algorithm or an empty object
*/
public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object;
/**
* Gets WebCrypto algorithm by wel-known OID
* @param oid algorithm identifier
* @param safety if `true` throws exception on unknown algorithm identifier
* @param target name of the target
* @returns Returns WebCrypto algorithm
* @throws Throws {@link Error} exception if unknown algorithm identifier
*/
public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T;
public getAlgorithmByOID(oid: string, safety = false, target?: string): any {
switch (oid) {
case "1.2.840.113549.1.1.1":
return {
name: "RSAES-PKCS1-v1_5"
};
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:
}
if (safety) {
throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`);
}
return {};
}
public getOIDByAlgorithm(algorithm: Algorithm, safety = false, target?: string): string {
let result = EMPTY_STRING;
switch (algorithm.name.toUpperCase()) {
case "RSAES-PKCS1-V1_5":
result = "1.2.840.113549.1.1.1";
break;
case "RSASSA-PKCS1-V1_5":
switch ((algorithm as any).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 as any).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 as any).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 as any).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 as any).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 as any).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 as any).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 as any).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:
}
if (!result && safety) {
throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`);
}
return result;
}
public getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams {
let result: type.CryptoEngineAlgorithmParams = {
algorithm: {},
usages: []
};
switch (algorithmName.toUpperCase()) {
case "RSAES-PKCS1-V1_5":
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 signatureAlgorithm Signature algorithm
*/
// TODO use safety
getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string {
let result = EMPTY_STRING;
switch (signatureAlgorithm.algorithmId) {
case "1.2.840.10045.4.1": // ecdsa-with-SHA1
case "1.2.840.113549.1.1.5": // rsa-encryption-with-SHA1
result = "SHA-1";
break;
case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256
case "1.2.840.113549.1.1.11": // rsa-encryption-with-SHA256
result = "SHA-256";
break;
case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384
case "1.2.840.113549.1.1.12": // rsa-encryption-with-SHA384
result = "SHA-384";
break;
case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512
case "1.2.840.113549.1.1.13": // rsa-encryption-with-SHA512
result = "SHA-512";
break;
case "1.2.840.113549.1.1.10": // RSA-PSS
{
try {
const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
if (params.hashAlgorithm) {
const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
if ("name" in algorithm) {
result = algorithm.name;
}
else {
return EMPTY_STRING;
}
}
else
result = "SHA-1";
}
catch {
// nothing
}
}
break;
default:
}
return result;
}
public async encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise<EncryptedContentInfo> {
//#region Check for input parameters
ParameterError.assert(parameters,
"password", "contentEncryptionAlgorithm", "hmacHashAlgorithm",
"iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType");
const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");
const pbkdf2OID = this.getOIDByAlgorithm({
name: "PBKDF2"
}, true, "PBKDF2");
const hmacOID = this.getOIDByAlgorithm({
name: "HMAC",
hash: {
name: parameters.hmacHashAlgorithm
}
} as Algorithm, true, "hmacHashAlgorithm");
//#endregion
//#region Initial variables
// TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer?
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
const passwordView = new Uint8Array(parameters.password);
const pbkdfKey = await this.importKey("raw",
passwordView,
"PBKDF2",
false,
["deriveKey"]);
//#endregion
//#region Derive key for "contentEncryptionAlgorithm"
const derivedKey = await this.deriveKey({
name: "PBKDF2",
hash: {
name: parameters.hmacHashAlgorithm
},
salt: saltView,
iterations: parameters.iterationCount
},
pbkdfKey,
parameters.contentEncryptionAlgorithm,
false,
["encrypt"]);
//#endregion
//#region Encrypt content
// TODO encrypt doesn't use all parameters from parameters.contentEncryptionAlgorithm (eg additionalData and tagLength for AES-GCM)
const encryptedData = await this.encrypt(
{
name: parameters.contentEncryptionAlgorithm.name,
iv: ivView
},
derivedKey,
contentView);
//#endregion
//#region Store all parameters in EncryptedData object
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: encryptedData })
});
//#endregion
}
/**
* Decrypt data stored in "EncryptedContentInfo" object using parameters
* @param parameters
*/
public async decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise<ArrayBuffer> {
//#region Check for input parameters
ParameterError.assert(parameters, "password", "encryptedContentInfo");
if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2
throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
//#endregion
//#region Initial variables
let pbes2Parameters: PBES2Params;
try {
pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams });
}
catch (ex) {
throw new Error("Incorrectly encoded \"pbes2Parameters\"");
}
let pbkdf2Params;
try {
pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
}
catch (ex) {
throw new Error("Incorrectly encoded \"pbkdf2Params\"");
}
const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true);
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 (pbkdf2Params.prf) {
const algorithm = this.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true);
hmacHashAlgorithm = algorithm.hash.name;
}
//#endregion
//#region Derive PBKDF2 key from "password" buffer
const pbkdfKey = await this.importKey("raw",
parameters.password,
"PBKDF2",
false,
["deriveKey"]);
//#endregion
//#region Derive key for "contentEncryptionAlgorithm"
const result = await this.deriveKey(
{
name: "PBKDF2",
hash: {
name: hmacHashAlgorithm
},
salt: saltView,
iterations: iterationCount
},
pbkdfKey,
contentEncryptionAlgorithm as any,
false,
["decrypt"]);
//#endregion
//#region Decrypt internal content using derived key
//#region Create correct data block for decryption
const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent();
//#endregion
return this.decrypt({
name: contentEncryptionAlgorithm.name,
iv: ivView
},
result,
dataBuffer);
//#endregion
}
public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer> {
//#region Check for input parameters
if ((parameters instanceof Object) === false)
throw new Error("Parameters must have type \"Object\"");
ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp");
//#endregion
//#region Choose correct length for HMAC key
let length: number;
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:
throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
}
//#endregion
//#region Initial variables
const hmacAlgorithm = {
name: "HMAC",
length,
hash: {
name: parameters.hashAlgorithm
}
};
//#endregion
//#region Create PKCS#12 key for integrity checking
const pkcsKey = await makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
//#endregion
//#region Import HMAC key
const hmacKey = await this.importKey("raw",
new Uint8Array(pkcsKey),
hmacAlgorithm,
false,
["sign"]);
//#endregion
//#region Make signed HMAC value
return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp));
//#endregion
}
public async verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean> {
//#region Check for input parameters
ParameterError.assert(parameters,
"password", "hashAlgorithm", "salt",
"iterationCount", "contentToVerify", "signatureToVerify");
//#endregion
//#region Choose correct length for HMAC key
let length = 0;
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:
throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
}
//#endregion
//#region Initial variables
const hmacAlgorithm = {
name: "HMAC",
length,
hash: {
name: parameters.hashAlgorithm
}
};
//#endregion
//#region Create PKCS#12 key for integrity checking
const pkcsKey = await makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
//#endregion
//#region Import HMAC key
const hmacKey = await this.importKey("raw",
new Uint8Array(pkcsKey),
hmacAlgorithm,
false,
["verify"]);
//#endregion
//#region Make signed HMAC value
return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify));
//#endregion
}
public async getSignatureParameters(privateKey: CryptoKey, hashAlgorithm = "SHA-1"): Promise<type.CryptoEngineSignatureParams> {
// Check hashing algorithm
this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
// Initial variables
const signatureAlgorithm = new AlgorithmIdentifier();
//#region Get a "default parameters" for current algorithm
const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
if (!Object.keys(parameters.algorithm).length) {
throw new Error("Parameter 'algorithm' is empty");
}
const algorithm = parameters.algorithm as any; // TODO remove `as any`
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(algorithm, true);
break;
case "RSA-PSS":
{
//#region Set "saltLength" as a length (in octets) of hash function result
switch (hashAlgorithm.toUpperCase()) {
case "SHA-256":
algorithm.saltLength = 32;
break;
case "SHA-384":
algorithm.saltLength = 48;
break;
case "SHA-512":
algorithm.saltLength = 64;
break;
default:
}
//#endregion
//#region Fill "RSASSA_PSS_params" object
const paramsObject: Partial<IRSASSAPSSParams> = {};
if (hashAlgorithm.toUpperCase() !== "SHA-1") {
const hashAlgorithmOID = this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "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 (algorithm.saltLength !== 20)
paramsObject.saltLength = 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:
throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
}
//#endregion
return {
signatureAlgorithm,
parameters
};
}
public async signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer> {
const signature = await this.sign(parameters.algorithm,
privateKey,
data);
//#region Special case for ECDSA algorithm
if (parameters.algorithm.name === "ECDSA") {
return common.createCMSECDSASignature(signature);
}
//#endregion
return signature;
}
public fillPublicKeyParameters(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier): type.CryptoEnginePublicKeyParams {
const parameters = {} as any;
//#region Find signer's hashing algorithm
const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
if (shaAlgorithm === EMPTY_STRING)
throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
//#endregion
//#region Get information about public key algorithm and default parameters for import
let algorithmId: string;
if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
algorithmId = signatureAlgorithm.algorithmId;
else
algorithmId = publicKeyInfo.algorithm.algorithmId;
const algorithmObject = this.getAlgorithmByOID(algorithmId, true);
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
const publicKeyAlgorithm = publicKeyInfo.algorithm;
if (!publicKeyAlgorithm.algorithmParams) {
throw new Error("Algorithm parameters for ECDSA public key are missed");
}
const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams;
if ("idBlock" in publicKeyAlgorithm.algorithmParams) {
if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) {
throw new Error("Incorrect type for ECDSA public key parameters");
}
}
const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true);
//#endregion
parameters.algorithm.algorithm.namedCurve = curveObject.name;
}
//#endregion
//#endregion
return parameters;
}
public async getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise<CryptoKey> {
if (!parameters) {
parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
}
const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false);
return this.importKey("spki",
publicKeyInfoBuffer,
parameters.algorithm.algorithm as Algorithm,
true,
parameters.algorithm.usages
);
}
public async verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean> {
//#region Find signer's hashing algorithm
let publicKey: CryptoKey;
if (!shaAlgorithm) {
shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
if (!shaAlgorithm)
throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
//#region Import public key
publicKey = await this.getPublicKey(publicKeyInfo, signatureAlgorithm);
//#endregion
} else {
const parameters = {} as type.CryptoEnginePublicKeyParams;
//#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, true);
parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
if ("hash" in parameters.algorithm.algorithm)
(parameters.algorithm.algorithm as any).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) {
throw new Error("Incorrect type for ECDSA public key parameters");
}
const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true);
//#endregion
(parameters.algorithm.algorithm as any).namedCurve = curveObject.name;
}
//#endregion
//#endregion
//#region Import public key
publicKey = await this.getPublicKey(publicKeyInfo, null as any, parameters); // TODO null!!!
//#endregion
}
//#endregion
//#region Verify signature
//#region Get default algorithm parameters for verification
const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
if ("hash" in algorithm.algorithm)
(algorithm.algorithm as any).hash.name = shaAlgorithm;
//#endregion
//#region Special case for ECDSA signatures
let signatureValue: BufferSource = signature.valueBlock.valueHexView;
if (publicKey.algorithm.name === "ECDSA") {
const namedCurve = ECNamedCurves.find((publicKey.algorithm as EcKeyAlgorithm).namedCurve);
if (!namedCurve) {
throw new Error("Unsupported named curve in use");
}
const asn1 = asn1js.fromBER(signatureValue);
AsnError.assert(asn1, "Signature value");
signatureValue = common.createECDSASignatureFromCMS(asn1.result, namedCurve.size);
}
//#endregion
//#region Special case for RSA-PSS
if (publicKey.algorithm.name === "RSA-PSS") {
const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
if ("saltLength" in pssParameters)
(algorithm.algorithm as any).saltLength = pssParameters.saltLength;
else
(algorithm.algorithm as any).saltLength = 20;
let hashAlgo = "SHA-1";
if ("hashAlgorithm" in pssParameters) {
const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true);
hashAlgo = hashAlgorithm.name;
}
(algorithm.algorithm as any).hash.name = hashAlgo;
}
//#endregion
return this.verify((algorithm.algorithm as any),
publicKey,
signatureValue,
data,
);
//#endregion
}
}