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

test: split test-crypto-keygen.js #49221

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
84 changes: 84 additions & 0 deletions test/common/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ if (!common.hasCrypto)

const assert = require('assert');
const crypto = require('crypto');
const {
createSign,
createVerify,
publicEncrypt,
privateDecrypt,
sign,
verify,
} = crypto;

// The values below (modp2/modp2buf) are for a 1024 bits long prime from
// RFC 2412 E.2, see https://tools.ietf.org/html/rfc2412. */
Expand Down Expand Up @@ -42,7 +50,83 @@ function testDH({ publicKey: alicePublicKey, privateKey: alicePrivateKey },
assert.deepStrictEqual(buf1, expectedValue);
}

// Asserts that the size of the given key (in chars or bytes) is within 10% of
// the expected size.
function assertApproximateSize(key, expectedSize) {
const u = typeof key === 'string' ? 'chars' : 'bytes';
const min = Math.floor(0.9 * expectedSize);
const max = Math.ceil(1.1 * expectedSize);
assert(key.length >= min,
`Key (${key.length} ${u}) is shorter than expected (${min} ${u})`);
assert(key.length <= max,
`Key (${key.length} ${u}) is longer than expected (${max} ${u})`);
}

// Tests that a key pair can be used for encryption / decryption.
function testEncryptDecrypt(publicKey, privateKey) {
const message = 'Hello Node.js world!';
const plaintext = Buffer.from(message, 'utf8');
for (const key of [publicKey, privateKey]) {
const ciphertext = publicEncrypt(key, plaintext);
const received = privateDecrypt(privateKey, ciphertext);
assert.strictEqual(received.toString('utf8'), message);
}
}

// Tests that a key pair can be used for signing / verification.
function testSignVerify(publicKey, privateKey) {
const message = Buffer.from('Hello Node.js world!');

function oldSign(algo, data, key) {
return createSign(algo).update(data).sign(key);
}

function oldVerify(algo, data, key, signature) {
return createVerify(algo).update(data).verify(key, signature);
}

for (const signFn of [sign, oldSign]) {
const signature = signFn('SHA256', message, privateKey);
for (const verifyFn of [verify, oldVerify]) {
for (const key of [publicKey, privateKey]) {
const okay = verifyFn('SHA256', message, key, signature);
assert(okay);
}
}
}
}
Comment on lines +65 to +97
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions have very generic names but very specific implementations. For example, testEncryptDecrypt works with certain asymmetric key pairs only, whereas most encryption and decryption operations in cryptography rely on symmetric keys.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a suggestions for the names? i think generally it's fine to have very generic names in the test commons - that's what we've been doing for many tests utils too. The point is just sharing code among tests. The were named that way in one long test test anyway, so keeping the name doesn't make much of a difference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The were named that way in one long test test anyway, so keeping the name doesn't make much of a difference.

Yes, in a test file that exclusively used asymmetric encryption. But moving them into common, these functions become available to all tests.

I don't want to make this more work for you than it has to be, so I am fine with leaving the names as they are.


// Constructs a regular expression for a PEM-encoded key with the given label.
function getRegExpForPEM(label, cipher) {
const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`;
const rfc1421Header = cipher == null ? '' :
`\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`;
const body = '([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}';
const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`;
return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`);
}

const pkcs1PubExp = getRegExpForPEM('RSA PUBLIC KEY');
const pkcs1PrivExp = getRegExpForPEM('RSA PRIVATE KEY');
const pkcs1EncExp = (cipher) => getRegExpForPEM('RSA PRIVATE KEY', cipher);
const spkiExp = getRegExpForPEM('PUBLIC KEY');
const pkcs8Exp = getRegExpForPEM('PRIVATE KEY');
const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY');
const sec1Exp = getRegExpForPEM('EC PRIVATE KEY');
const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);

module.exports = {
modp2buf,
testDH,
assertApproximateSize,
testEncryptDecrypt,
testSignVerify,
pkcs1PubExp,
pkcs1PrivExp,
pkcs1EncExp, // used once
spkiExp,
pkcs8Exp, // used once
pkcs8EncExp, // used once
sec1Exp,
sec1EncExp,
};
32 changes: 32 additions & 0 deletions test/parallel/test-crypto-keygen-async-dsa-key-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');

// Test async DSA key object generation.
{
generateKeyPair('dsa', {
modulusLength: common.hasOpenSSL3 ? 2048 : 512,
divisorLength: 256
}, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
modulusLength: common.hasOpenSSL3 ? 2048 : 512,
divisorLength: 256
});

assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
modulusLength: common.hasOpenSSL3 ? 2048 : 512,
divisorLength: 256
});
}));
}
64 changes: 64 additions & 0 deletions test/parallel/test-crypto-keygen-async-dsa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');
const {
assertApproximateSize,
testSignVerify,
spkiExp,
} = require('../common/crypto');

// Test async DSA key generation.
{
const privateKeyEncoding = {
type: 'pkcs8',
format: 'der'
};

generateKeyPair('dsa', {
modulusLength: common.hasOpenSSL3 ? 2048 : 512,
divisorLength: 256,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
cipher: 'aes-128-cbc',
passphrase: 'secret',
...privateKeyEncoding
}
}, common.mustSucceed((publicKey, privateKeyDER) => {
assert.strictEqual(typeof publicKey, 'string');
assert.match(publicKey, spkiExp);
// The private key is DER-encoded.
assert(Buffer.isBuffer(privateKeyDER));

assertApproximateSize(publicKey, common.hasOpenSSL3 ? 1194 : 440);
assertApproximateSize(privateKeyDER, common.hasOpenSSL3 ? 721 : 336);

// Since the private key is encrypted, signing shouldn't work anymore.
assert.throws(() => {
return testSignVerify(publicKey, {
key: privateKeyDER,
...privateKeyEncoding
});
}, {
name: 'TypeError',
code: 'ERR_MISSING_PASSPHRASE',
message: 'Passphrase required for encrypted key'
});

// Signing should work with the correct password.
testSignVerify(publicKey, {
key: privateKeyDER,
...privateKeyEncoding,
passphrase: 'secret'
});
}));
}
35 changes: 35 additions & 0 deletions test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');

// Test async elliptic curve key generation with 'jwk' encoding and named
// curve.
['P-384', 'P-256', 'P-521', 'secp256k1'].forEach((curve) => {
generateKeyPair('ec', {
namedCurve: curve,
publicKeyEncoding: {
format: 'jwk'
},
privateKeyEncoding: {
format: 'jwk'
}
}, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(typeof publicKey, 'object');
assert.strictEqual(typeof privateKey, 'object');
assert.strictEqual(publicKey.x, privateKey.x);
assert.strictEqual(publicKey.y, privateKey.y);
assert(!publicKey.d);
assert(privateKey.d);
assert.strictEqual(publicKey.kty, 'EC');
assert.strictEqual(publicKey.kty, privateKey.kty);
assert.strictEqual(publicKey.crv, curve);
assert.strictEqual(publicKey.crv, privateKey.crv);
}));
});
38 changes: 38 additions & 0 deletions test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');

// Test async elliptic curve key generation with 'jwk' encoding and RSA.
{
generateKeyPair('rsa', {
modulusLength: 1024,
publicKeyEncoding: {
format: 'jwk'
},
privateKeyEncoding: {
format: 'jwk'
}
}, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(typeof publicKey, 'object');
assert.strictEqual(typeof privateKey, 'object');
assert.strictEqual(publicKey.kty, 'RSA');
assert.strictEqual(publicKey.kty, privateKey.kty);
assert.strictEqual(typeof publicKey.n, 'string');
assert.strictEqual(publicKey.n, privateKey.n);
assert.strictEqual(typeof publicKey.e, 'string');
assert.strictEqual(publicKey.e, privateKey.e);
assert.strictEqual(typeof privateKey.d, 'string');
assert.strictEqual(typeof privateKey.p, 'string');
assert.strictEqual(typeof privateKey.q, 'string');
assert.strictEqual(typeof privateKey.dp, 'string');
assert.strictEqual(typeof privateKey.dq, 'string');
assert.strictEqual(typeof privateKey.qi, 'string');
}));
}
40 changes: 40 additions & 0 deletions test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');

// Test async elliptic curve key generation with 'jwk' encoding.
{
[
'ed25519',
'ed448',
'x25519',
'x448',
].forEach((type) => {
generateKeyPair(type, {
publicKeyEncoding: {
format: 'jwk'
},
privateKeyEncoding: {
format: 'jwk'
}
}, common.mustSucceed((publicKey, privateKey) => {
assert.strictEqual(typeof publicKey, 'object');
assert.strictEqual(typeof privateKey, 'object');
assert.strictEqual(publicKey.x, privateKey.x);
assert(!publicKey.d);
assert(privateKey.d);
assert.strictEqual(publicKey.kty, 'OKP');
assert.strictEqual(publicKey.kty, privateKey.kty);
const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
assert.strictEqual(publicKey.crv, expectedCrv);
assert.strictEqual(publicKey.crv, privateKey.crv);
}));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');
const {
assertApproximateSize,
testEncryptDecrypt,
testSignVerify,
} = require('../common/crypto');

// Test async RSA key generation with an encrypted private key, but encoded as DER.
{
generateKeyPair('rsa', {
publicExponent: 0x10001,
modulusLength: 512,
publicKeyEncoding: {
type: 'pkcs1',
format: 'der'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'der'
}
}, common.mustSucceed((publicKeyDER, privateKeyDER) => {
assert(Buffer.isBuffer(publicKeyDER));
assertApproximateSize(publicKeyDER, 74);

assert(Buffer.isBuffer(privateKeyDER));

const publicKey = {
key: publicKeyDER,
type: 'pkcs1',
format: 'der',
};
const privateKey = {
key: privateKeyDER,
format: 'der',
type: 'pkcs8',
passphrase: 'secret'
};
testEncryptDecrypt(publicKey, privateKey);
testSignVerify(publicKey, privateKey);
}));
}