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: thrownew 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];
}
//#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);
//#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);
//#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 = [];
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 {
if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1") thrownew Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
//#region Get information about used hash function if (!jwk.alg) { if (!alg.hash) { thrownew 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: thrownew 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") { thrownew Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
} //#endregion
//#region Get information about used hash function if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1") thrownew 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: thrownew 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") thrownew Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); //#endregion
//#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 { returnthis.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages);
} catch { returnthis.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
}
} //#endregion
/** * 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
// 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);
//#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 })
})
});
public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer> { //#region Check for input parameters if ((parameters instanceof Object) === false) thrownew Error("Parameters must have type \"Object\"");
//#region Make signed HMAC value returnthis.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)); //#endregion
}
// 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) { thrownew 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> = {};
//#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 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;
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) { thrownew 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))) { thrownew Error("Incorrect type for ECDSA public key parameters");
}
}
returnthis.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) thrownew 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;
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) { thrownew Error("Incorrect type for ECDSA public key parameters");
}
(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) { thrownew 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
returnthis.verify((algorithm.algorithm as any),
publicKey,
signatureValue,
data,
); //#endregion
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.49 Sekunden
(vorverarbeitet am 2026-06-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.