UNPKG

selfsigned

Version:

Generate self signed certificates private and public keys

413 lines (332 loc) 16.3 kB
var { assert } = require('chai'); var fs = require('fs'); var { promisify } = require('util'); var exec = promisify(require('child_process').exec); var crypto = require('crypto'); describe('generate', function () { var generate = require('../index').generate; var { createPkcs7 } = require('../pkcs7'); it('should work without attrs/options', async function () { var pems = await generate(); assert.ok(!!pems.private, 'has a private key'); assert.ok(!!pems.fingerprint, 'has fingerprint'); assert.ok(!!pems.public, 'has a public key'); assert.ok(!!pems.cert, 'has a certificate'); assert.ok(!pems.pkcs7, 'should not include a pkcs7 by default'); assert.ok(!pems.clientcert, 'should not include a client cert by default'); assert.ok(!pems.clientprivate, 'should not include a client private key by default'); assert.ok(!pems.clientpublic, 'should not include a client public key by default'); // Verify cert can be read by Node.js crypto const cert = new crypto.X509Certificate(pems.cert); assert.ok(cert.subject, 'cert has a subject'); }); it('should generate client cert', async function () { var pems = await generate(null, {clientCertificate: true}); assert.ok(!!pems.clientcert, 'should include a client cert when requested'); assert.ok(!!pems.clientprivate, 'should include a client private key when requested'); assert.ok(!!pems.clientpublic, 'should include a client public key when requested'); }); it('should include pkcs7', async function () { var pems = await generate([{ name: 'commonName', value: 'contoso.com' }]); var pkcs7 = createPkcs7(pems.cert); assert.ok(!!pkcs7, 'has a pkcs7'); try { fs.unlinkSync('/tmp/tmp.pkcs7'); } catch (er) {} fs.writeFileSync('/tmp/tmp.pkcs7', pkcs7); const { stdout, stderr } = await exec('openssl pkcs7 -print_certs -in /tmp/tmp.pkcs7'); if (stderr && stderr.length) { throw new Error(stderr); } const expected = stdout.toString(); let [ subjectLine,issuerLine, ...cert ] = expected.split(/\r?\n/).filter(c => c); cert = cert.filter(c => c); assert.match(subjectLine, /subject=\/?CN\s?=\s?contoso.com/i); assert.match(issuerLine, /issuer=\/?CN\s?=\s?contoso.com/i); // Normalize line endings for comparison const normalizedPemCert = pems.cert.replace(/\r\n/g, '\n').trim(); const normalizedExpected = cert.join('\n').trim(); assert.strictEqual( normalizedPemCert, normalizedExpected ); }); it('should support sha1 algorithm', async function () { var pems_sha1 = await generate(null, { algorithm: 'sha1' }); const cert = new crypto.X509Certificate(pems_sha1.cert); // SHA-1 with RSA signature assert.ok(cert.publicKey, 'can generate sha1 certs'); }); it('should support sha256 algorithm', async function () { var pems_sha256 = await generate(null, { algorithm: 'sha256' }); const cert = new crypto.X509Certificate(pems_sha256.cert); // SHA-256 with RSA signature assert.ok(cert.publicKey, 'can generate sha256 certs'); }); it('should default to 2048 bit keysize', async function () { var pems = await generate(); const privateKey = crypto.createPrivateKey(pems.private); const keyDetails = privateKey.asymmetricKeyDetails; assert.strictEqual(keyDetails.modulusLength, 2048, 'default key size should be 2048 bits'); }); it('should default to 2048 bit keysize for client certificate', async function () { var pems = await generate(null, {clientCertificate: true}); const clientPrivateKey = crypto.createPrivateKey(pems.clientprivate); const keyDetails = clientPrivateKey.asymmetricKeyDetails; assert.strictEqual(keyDetails.modulusLength, 2048, 'default client key size should be 2048 bits'); }); it('should support custom keySize', async function () { var pems = await generate(null, { keySize: 4096 }); const privateKey = crypto.createPrivateKey(pems.private); const keyDetails = privateKey.asymmetricKeyDetails; assert.strictEqual(keyDetails.modulusLength, 4096, 'should support custom key size'); }); it('should support custom clientCertificateKeySize', async function () { var pems = await generate(null, { clientCertificate: true, clientCertificateKeySize: 4096 }); const clientPrivateKey = crypto.createPrivateKey(pems.clientprivate); const keyDetails = clientPrivateKey.asymmetricKeyDetails; assert.strictEqual(keyDetails.modulusLength, 4096, 'should support custom client key size'); }); it('should support sha384 algorithm', async function () { var pems = await generate(null, { algorithm: 'sha384' }); const cert = new crypto.X509Certificate(pems.cert); assert.ok(cert.publicKey, 'can generate sha384 certs'); }); it('should support sha512 algorithm', async function () { var pems = await generate(null, { algorithm: 'sha512' }); const cert = new crypto.X509Certificate(pems.cert); assert.ok(cert.publicKey, 'can generate sha512 certs'); }); it('should default to 365 days validity', async function () { var pems = await generate(); const cert = new crypto.X509Certificate(pems.cert); const validFrom = new Date(cert.validFrom); const validTo = new Date(cert.validTo); const diffTime = Math.abs(validTo - validFrom); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); assert.approximately(diffDays, 365, 1, 'certificate should default to 365 days validity'); }); it('should respect notBeforeDate option', async function () { const customDate = new Date('2025-01-01T00:00:00Z'); var pems = await generate(null, { notBeforeDate: customDate }); const cert = new crypto.X509Certificate(pems.cert); const validFrom = new Date(cert.validFrom); // Allow small difference for processing time assert.approximately(validFrom.getTime(), customDate.getTime(), 5000, 'should use custom notBeforeDate'); }); it('should respect notAfterDate option', async function () { const notBefore = new Date('2025-01-01T00:00:00Z'); const notAfter = new Date('2025-02-15T00:00:00Z'); var pems = await generate(null, { notBeforeDate: notBefore, notAfterDate: notAfter }); const cert = new crypto.X509Certificate(pems.cert); const validFrom = new Date(cert.validFrom); const validTo = new Date(cert.validTo); assert.approximately(validFrom.getTime(), notBefore.getTime(), 5000, 'should use custom notBeforeDate'); assert.approximately(validTo.getTime(), notAfter.getTime(), 5000, 'should use custom notAfterDate'); }); it('should generate valid fingerprint format', async function () { var pems = await generate(); assert.match(pems.fingerprint, /^[0-9a-f]{2}(:[0-9a-f]{2}){19}$/i, 'fingerprint should be valid SHA-1 format'); }); it('should support custom attributes', async function () { const attrs = [ { name: 'commonName', value: 'test.example.com' }, { name: 'countryName', value: 'GB' }, { shortName: 'ST', value: 'London' }, { name: 'localityName', value: 'Westminster' }, { name: 'organizationName', value: 'Test Corp' }, { shortName: 'OU', value: 'Engineering' } ]; var pems = await generate(attrs); const cert = new crypto.X509Certificate(pems.cert); assert.include(cert.subject, 'CN=test.example.com', 'should include custom CN'); assert.include(cert.subject, 'C=GB', 'should include custom country'); assert.include(cert.subject, 'O=Test Corp', 'should include custom organization'); }); it('should support custom clientCertificateCN (deprecated)', async function () { var pems = await generate(null, { clientCertificate: true, clientCertificateCN: 'Custom User CN' }); const clientCert = new crypto.X509Certificate(pems.clientcert); assert.include(clientCert.subject, 'CN=Custom User CN', 'should use custom client CN'); }); it('should support clientCertificate as options object with cn', async function () { var pems = await generate(null, { clientCertificate: { cn: 'Client Options CN' } }); const clientCert = new crypto.X509Certificate(pems.clientcert); assert.include(clientCert.subject, 'CN=Client Options CN', 'should use cn from clientCertificate options'); }); it('should support clientCertificate.keySize', async function () { var pems = await generate(null, { clientCertificate: { keySize: 4096 } }); const clientPrivateKey = crypto.createPrivateKey(pems.clientprivate); const keyDetails = clientPrivateKey.asymmetricKeyDetails; assert.strictEqual(keyDetails.modulusLength, 4096, 'should use keySize from clientCertificate options'); }); it('should support clientCertificate.notBeforeDate and notAfterDate', async function () { const notBefore = new Date('2025-06-01T00:00:00Z'); const notAfter = new Date('2025-06-30T00:00:00Z'); var pems = await generate(null, { clientCertificate: { notBeforeDate: notBefore, notAfterDate: notAfter } }); const clientCert = new crypto.X509Certificate(pems.clientcert); const validFrom = new Date(clientCert.validFrom); const validTo = new Date(clientCert.validTo); assert.approximately(validFrom.getTime(), notBefore.getTime(), 5000, 'should use notBeforeDate from clientCertificate options'); assert.approximately(validTo.getTime(), notAfter.getTime(), 5000, 'should use notAfterDate from clientCertificate options'); }); it('should support clientCertificate.algorithm', async function () { var pems = await generate(null, { algorithm: 'sha1', // main cert uses sha1 clientCertificate: { algorithm: 'sha256' // client cert uses sha256 } }); // Both certs should be valid const serverCert = new crypto.X509Certificate(pems.cert); const clientCert = new crypto.X509Certificate(pems.clientcert); assert.ok(serverCert.publicKey, 'server cert should be valid'); assert.ok(clientCert.publicKey, 'client cert should be valid'); }); it('clientCertificate options should take precedence over deprecated options', async function () { var pems = await generate(null, { clientCertificateCN: 'Deprecated CN', clientCertificateKeySize: 2048, clientCertificate: { cn: 'New Options CN', keySize: 4096 } }); const clientCert = new crypto.X509Certificate(pems.clientcert); assert.include(clientCert.subject, 'CN=New Options CN', 'clientCertificate.cn should take precedence'); const clientPrivateKey = crypto.createPrivateKey(pems.clientprivate); const keyDetails = clientPrivateKey.asymmetricKeyDetails; assert.strictEqual(keyDetails.modulusLength, 4096, 'clientCertificate.keySize should take precedence'); }); it('should generate valid key pair that work together', async function () { var pems = await generate(); // Test data const testData = 'Hello, World!'; // Create sign and verify objects const privateKey = crypto.createPrivateKey(pems.private); const publicKey = crypto.createPublicKey(pems.public); // Sign with private key const sign = crypto.createSign('SHA256'); sign.update(testData); sign.end(); const signature = sign.sign(privateKey); // Verify with public key const verify = crypto.createVerify('SHA256'); verify.update(testData); verify.end(); const isValid = verify.verify(publicKey, signature); assert.isTrue(isValid, 'public key should verify signature from private key'); }); it('should create client cert signed by server cert', async function () { var pems = await generate(null, { clientCertificate: true }); const serverCert = new crypto.X509Certificate(pems.cert); const clientCert = new crypto.X509Certificate(pems.clientcert); // Client cert should have different subject than server assert.notEqual(clientCert.subject, serverCert.subject, 'client and server should have different subjects'); // Both certs should be valid assert.ok(serverCert.publicKey, 'server cert should be valid'); assert.ok(clientCert.publicKey, 'client cert should be valid'); }); it('should support using existing keyPair', async function () { // First generate a key pair const firstPems = await generate(); // Reuse the key pair const secondPems = await generate(null, { keyPair: { privateKey: firstPems.private, publicKey: firstPems.public } }); // Keys should be identical assert.strictEqual(firstPems.private, secondPems.private, 'should use provided private key'); assert.strictEqual(firstPems.public, secondPems.public, 'should use provided public key'); // Certificates will be different (different serial, dates) but keys are same const firstCert = new crypto.X509Certificate(firstPems.cert); const secondCert = new crypto.X509Certificate(secondPems.cert); assert.strictEqual(firstCert.publicKey.export({ format: 'pem', type: 'spki' }), secondCert.publicKey.export({ format: 'pem', type: 'spki' }), 'certificates should contain the same public key'); }); it('should create PKCS#7 for client certificate', async function () { var pems = await generate([{ name: 'commonName', value: 'server.example.com' }], { clientCertificate: true }); var clientPkcs7 = createPkcs7(pems.clientcert); assert.ok(!!clientPkcs7, 'should create PKCS#7 for client cert'); assert.include(clientPkcs7, 'BEGIN PKCS7', 'should be valid PKCS#7 format'); // Verify with openssl try { fs.unlinkSync('/tmp/tmp-client.pkcs7'); } catch (er) {} fs.writeFileSync('/tmp/tmp-client.pkcs7', clientPkcs7); const { stdout, stderr } = await exec('openssl pkcs7 -print_certs -in /tmp/tmp-client.pkcs7'); if (stderr && stderr.length) { throw new Error(stderr); } assert.ok(stdout.toString().length > 0, 'openssl should be able to read client PKCS#7'); }); it('should generate unique serial numbers', async function () { const pems1 = await generate(); const pems2 = await generate(); const cert1 = new crypto.X509Certificate(pems1.cert); const cert2 = new crypto.X509Certificate(pems2.cert); assert.notEqual(cert1.serialNumber, cert2.serialNumber, 'serial numbers should be unique'); }); it('should handle minimal attributes', async function () { const attrs = [{ name: 'commonName', value: 'minimal.test' }]; var pems = await generate(attrs); const cert = new crypto.X509Certificate(pems.cert); assert.include(cert.subject, 'CN=minimal.test', 'should work with minimal attributes'); }); it('should support passphrase for private key encryption', async function () { const passphrase = 'my-secret-passphrase'; var pems = await generate(null, { passphrase: passphrase }); assert.ok(!!pems.private, 'has a private key'); assert.include(pems.private, 'ENCRYPTED', 'private key should be encrypted'); // Verify the key can be decrypted with the correct passphrase const privateKey = crypto.createPrivateKey({ key: pems.private, passphrase: passphrase }); assert.ok(privateKey, 'should be able to decrypt private key with passphrase'); // Verify signing works with decrypted key const testData = 'Hello, World!'; const sign = crypto.createSign('SHA256'); sign.update(testData); sign.end(); const signature = sign.sign({ key: pems.private, passphrase: passphrase }); const verify = crypto.createVerify('SHA256'); verify.update(testData); verify.end(); const isValid = verify.verify(pems.public, signature); assert.isTrue(isValid, 'encrypted key should work for signing'); }); it('should fail to decrypt private key with wrong passphrase', async function () { const passphrase = 'correct-passphrase'; var pems = await generate(null, { passphrase: passphrase }); assert.throws(() => { crypto.createPrivateKey({ key: pems.private, passphrase: 'wrong-passphrase' }); }); }); });