Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow PKCS #12 generation #403

Open
pboguslawski opened this issue Apr 9, 2024 · 0 comments
Open

Slow PKCS #12 generation #403

pboguslawski opened this issue Apr 9, 2024 · 0 comments

Comments

@pboguslawski
Copy link

PKCS #12 generating with 600k iterations with code below (similar to openSSLLike example) takes 49-60s (tested in Firefox 115.9.1esr 64bit and Chromium 90 64-bit). Tested that most of the execution time is eaten by last makeInternalValues call (integrity protection envelope).

PKCS #12 generating with 600k iterations on same machine with openssl 3 from Debian 12 takes less than 1s:

$ time openssl pkcs12 -export -in test.crt -inkey test.key -out test.p12 -name 'test' -password pass:1234 -iter 600000

real	0m0.917s
user	0m0.894s
sys	0m0.000s

Is it expected or PKI.js bug?

PKCS #12 generation function code using PKI.js (PBKDF2_ITERATION_COUNT is set to 600000 during test):

// downloadPKCS12 downloads given private key and certificate in encrypted PKCS #12 file.
export async function downloadPKCS12(
	keyPair: CryptoKeyPair,
	certificate: Certificate,
	ownerId: string,
	password: string,
	filename: string
) {
	if (!password) {
		throw new Error('password cannot be empty');
	}
	if (!ownerId) {
		throw new Error('ownerId cannot be empty');
	}
	if (!filename) {
		throw new Error('filename cannot be empty');
	}

	const crypto = getCrypto(true);
	const passwordConverted = Convert.FromUtf8String(password);
	const certFingerprint = await crypto.digest('SHA-1', certificate.toSchema().toBER(false));
	const privateKeyBinary = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey);
	const pkcs8Simpl = new PrivateKeyInfo({ schema: fromBER(privateKeyBinary).result });

	// Put initial values for PKCS#12 structures.
	const pkcs12 = new PFX({
		parsedValue: {
			integrityMode: 0, // Password-Based Integrity Mode.
			authenticatedSafe: new AuthenticatedSafe({
				parsedValue: {
					safeContents: [
						{
							privacyMode: 1, // Password-Based Privacy Protection Mode.
							value: new SafeContents({
								safeBags: [
									new SafeBag({
										bagId: '1.2.840.113549.1.12.10.1.3',
										bagValue: new CertBag({
											parsedValue: certificate
										}),
										bagAttributes: [
											new Attribute({
												type: '1.2.840.113549.1.9.21', // localKeyID
												values: [new OctetString({ valueHex: certFingerprint })]
											}),
											new Attribute({
												type: '1.2.840.113549.1.9.20', // friendlyName
												values: [new BmpString({ value: ownerId })]
											})
										]
									})
								]
							})
						},
						{
							privacyMode: 0, // No-privacy Protection Mode.
							value: new SafeContents({
								safeBags: [
									new SafeBag({
										bagId: '1.2.840.113549.1.12.10.1.2',
										bagValue: new PKCS8ShroudedKeyBag({
											parsedValue: pkcs8Simpl
										}),
										bagAttributes: [
											new Attribute({
												type: '1.2.840.113549.1.9.21', // localKeyID
												values: [new OctetString({ valueHex: certFingerprint })]
											}),
											new Attribute({
												type: '1.2.840.113549.1.9.20', // friendlyName
												values: [new BmpString({ value: ownerId })]
											})
										]
									})
								]
							})
						}
					]
				}
			})
		}
	});

	// Encode internal values for PKCS8ShroudedKeyBag.
	if (!(pkcs12.parsedValue && pkcs12.parsedValue.authenticatedSafe)) {
		throw new Error('pkcs12.parsedValue.authenticatedSafe is empty');
	}
	await pkcs12.parsedValue.authenticatedSafe.parsedValue.safeContents[1].value.safeBags[0].bagValue.makeInternalValues(
		{
			password: passwordConverted,
			contentEncryptionAlgorithm: {
				name: 'AES-CBC', // OpenSSL can handle AES-CBC only.
				length: 256
			},
			hmacHashAlgorithm: 'SHA-256',
			iterationCount: PBKDF2_ITERATION_COUNT
		}
	);

	// Encode internal values for all SafeContents first (create all Privacy Protection envelopes).
	await pkcs12.parsedValue.authenticatedSafe.makeInternalValues({
		safeContents: [
			{
				password: passwordConverted,
				contentEncryptionAlgorithm: {
					name: 'AES-CBC', // OpenSSL can handle AES-CBC only.
					length: 256
				},
				hmacHashAlgorithm: 'SHA-256',
				iterationCount: PBKDF2_ITERATION_COUNT
			},
			{
				// Empty parameters for second SafeContent since No Privacy protection mode there.
			}
		]
	});

	// Encode internal values for Integrity Protection envelope.
	await pkcs12.makeInternalValues({
		password: passwordConverted,
		iterations: PBKDF2_ITERATION_COUNT, // Big value here causes long generation time.
		pbkdf2HashAlgorithm: 'SHA-256', // Least two parameters are equal because at the moment it is not clear how to use PBMAC1 schema with PKCS#12 integrity protection.
		hmacHashAlgorithm: 'SHA-256'
	});

	// Download prepared PKCS #12 content to file.
	downloadFile(filename, 'application/pkcs12', pkcs12.toSchema().toBER(false));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant