🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

selfsigned

Package Overview
Dependencies
Maintainers
2
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

selfsigned - npm Package Compare versions

Comparing version
4.0.1
to
5.0.0
+97
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.0.0] - 2025-11-26
### 🚀 Major Rewrite
Complete rewrite replacing `node-forge` with modern `@peculiar/x509` and `pkijs` libraries.
### ✨ Added
- Native WebCrypto API support for better performance and security
- TypeScript examples in documentation
- Async/await support as the primary API
- Support for `keyPair` option to use existing keys
- Updated to use Node.js native crypto for all operations
- Separate `selfsigned/pkcs7` module for tree-shakeable PKCS#7 support
### 💥 BREAKING CHANGES
1. **Async-only API**: The `generate()` function now returns a Promise. Synchronous generation has been removed.
```js
// Old (v4.x)
const pems = selfsigned.generate(attrs, options);
// New (v5.x)
const pems = await selfsigned.generate(attrs, options);
```
2. **No callback support**: Callbacks have been completely removed in favor of Promises.
```js
// Old (v4.x)
selfsigned.generate(attrs, options, function(err, pems) { ... });
// New (v5.x)
const pems = await selfsigned.generate(attrs, options);
```
3. **Minimum Node.js version**: Now requires Node.js >= 15.6.0 (was >= 10)
- Required for native WebCrypto support
4. **Dependencies changed**:
- ❌ Removed: `node-forge` (1.64 MB)
- ✅ Added: `@peculiar/x509` (551 KB) - 66% smaller!
- ✅ Added: `pkijs` (1.94 MB, only for PKCS#7 support)
- Bundle size reduced by 66% when not using PKCS#7
5. **PKCS#7 API changed**:
- Old: `const pems = await generate(attrs, { pkcs7: true }); pems.pkcs7`
- New: `const { createPkcs7 } = require('selfsigned/pkcs7'); const pkcs7 = createPkcs7(pems.cert);`
- PKCS#7 is now a separate module for better tree-shaking
### 🔧 Changed
- Default key size remains 2048 bits (was incorrectly documented as 1024)
- PEM output uses `\n` line endings (was `\r\n`)
- Private keys now use PKCS#8 format (`BEGIN PRIVATE KEY` instead of `BEGIN RSA PRIVATE KEY`)
- Certificate generation is now fully async using native WebCrypto
- **PKCS#7 is now tree-shakeable**: Moved to separate `selfsigned/pkcs7` module so bundlers can exclude it when not used
### 🐛 Fixed
- Default key size documentation corrected from 1024 to 2048 bits
- Improved error handling for certificate generation failures
### 📦 Dependencies
**Removed:**
- `node-forge@^1.3.1`
- `@types/node-forge@^1.3.0`
**Added:**
- `@peculiar/x509@^1.14.2` (required)
- `pkijs@^3.3.3` (required, but tree-shakeable via separate `selfsigned/pkcs7` module)
### 🔒 Security
- Now uses Node.js native WebCrypto API instead of JavaScript implementation
- Better integration with platform security features
- More secure random number generation
### 📚 Documentation
- Complete README rewrite with async/await examples
- Added migration guide from v4.x to v5.x
- Updated all code examples to use async/await
- Added requirements section highlighting Node.js version requirement
---
## [4.0.0] - Previous Release
See git history for changes in 4.x and earlier versions.
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const selfsigned = require('../');
async function main() {
// Get mkcert's CAROOT path
let caroot;
try {
caroot = execSync('mkcert -CAROOT', { encoding: 'utf8' }).trim();
} catch (err) {
console.error('Error: mkcert is not installed or not in PATH');
console.error('Install mkcert: https://github.com/FiloSottile/mkcert');
process.exit(1);
}
const caKeyPath = path.join(caroot, 'rootCA-key.pem');
const caCertPath = path.join(caroot, 'rootCA.pem');
// Check if CA files exist
if (!fs.existsSync(caKeyPath) || !fs.existsSync(caCertPath)) {
console.error('Error: mkcert CA files not found');
console.error('Run "mkcert -install" first to create the local CA');
process.exit(1);
}
console.log('Using mkcert CA from:', caroot);
// Read CA certificate and key
const caKey = fs.readFileSync(caKeyPath, 'utf8');
const caCert = fs.readFileSync(caCertPath, 'utf8');
// Generate a certificate signed by mkcert's CA
const pems = await selfsigned.generate([
{ name: 'commonName', value: 'localhost' }
], {
days: 365,
keySize: 2048,
algorithm: 'sha256',
ca: {
key: caKey,
cert: caCert
}
});
// Create HTTPS server with the generated certificate
const server = https.createServer({
key: pems.private,
cert: pems.cert
}, (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from HTTPS server with mkcert CA!\n');
});
const port = 3443;
server.listen(port, () => {
console.log(`HTTPS server running at https://localhost:${port}/`);
console.log('Certificate fingerprint:', pems.fingerprint);
console.log('\nSince this certificate is signed by mkcert\'s CA,');
console.log('your browser should trust it automatically (if mkcert -install was run).');
console.log('\nTest with: curl https://localhost:' + port);
});
}
main().catch(console.error);
const https = require('https');
const selfsigned = require('../');
async function main() {
// Generate a self-signed certificate
const pems = await selfsigned.generate([
{ name: 'commonName', value: 'localhost' }
], {
days: 365,
keySize: 2048,
algorithm: 'sha256'
});
// Create HTTPS server with the generated certificate
const server = https.createServer({
key: pems.private,
cert: pems.cert
}, (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from self-signed HTTPS server!\n');
});
const port = 3443;
server.listen(port, () => {
console.log(`HTTPS server running at https://localhost:${port}/`);
console.log('Certificate fingerprint:', pems.fingerprint);
console.log('\nNote: Your browser will warn about the self-signed certificate.');
console.log('Test with: curl -k https://localhost:' + port);
});
}
main().catch(console.error);
const pkijs = require("pkijs");
const nodeCrypto = require("crypto");
// Use Node.js native webcrypto
const crypto = nodeCrypto.webcrypto;
// Set up pkijs to use native crypto
// Note: This modifies global pkijs state. If the consumer also uses pkijs,
// they should set their own engine or use a version that supports per-instance engines.
let pkijsInitialized = false;
function ensurePkijsInitialized() {
if (!pkijsInitialized) {
pkijs.setEngine("nodeEngine", crypto, new pkijs.CryptoEngine({
name: "",
crypto: crypto,
subtle: crypto.subtle
}));
pkijsInitialized = true;
}
}
/**
* Create PKCS#7 formatted certificate from PEM certificate
*
* @param {string} certPem - PEM formatted certificate
* @returns {string} PKCS#7 PEM formatted certificate
*/
function createPkcs7(certPem) {
ensurePkijsInitialized();
// Parse the PEM certificate to get raw data
const certLines = certPem.split('\n').filter(line =>
!line.includes('BEGIN CERTIFICATE') &&
!line.includes('END CERTIFICATE') &&
line.trim()
);
const certBase64 = certLines.join('');
const certBuffer = Buffer.from(certBase64, 'base64');
// Parse certificate using pkijs
const asn1Cert = pkijs.Certificate.fromBER(certBuffer);
// Create PKCS#7 SignedData structure
const cmsSigned = new pkijs.SignedData({
version: 1,
encapContentInfo: new pkijs.EncapsulatedContentInfo({
eContentType: "1.2.840.113549.1.7.1" // data
}),
certificates: [asn1Cert]
});
// Wrap in ContentInfo
const cmsSignedSchema = cmsSigned.toSchema();
const cmsContentInfo = new pkijs.ContentInfo({
contentType: "1.2.840.113549.1.7.2", // signedData
content: cmsSignedSchema
});
// Convert to DER and then PEM
const cmsSignedDer = cmsContentInfo.toSchema().toBER(false);
const pkcs7Pem =
'-----BEGIN PKCS7-----\n' +
Buffer.from(cmsSignedDer).toString('base64').match(/.{1,64}/g).join('\n') +
'\n-----END PKCS7-----\n';
return pkcs7Pem;
}
module.exports = { createPkcs7 };
var { assert } = require('chai');
var crypto = require('crypto');
describe('CA signing', function () {
var generate = require('../index').generate;
it('should generate certificate signed by provided CA', async function () {
// First generate a self-signed CA certificate
const ca = await generate([
{ name: 'commonName', value: 'Test CA' },
{ name: 'organizationName', value: 'Test Organization' }
], {
algorithm: 'sha256'
});
// Generate a certificate signed by the CA
const pems = await generate([
{ name: 'commonName', value: 'localhost' }
], {
algorithm: 'sha256',
ca: {
key: ca.private,
cert: ca.cert
}
});
assert.ok(!!pems.private, 'has a private key');
assert.ok(!!pems.public, 'has a public key');
assert.ok(!!pems.cert, 'has a certificate');
assert.ok(!!pems.fingerprint, 'has fingerprint');
const cert = new crypto.X509Certificate(pems.cert);
const caCert = new crypto.X509Certificate(ca.cert);
// Verify issuer is the CA, not self-signed
assert.include(cert.issuer, 'CN=Test CA', 'issuer should be the CA');
assert.include(cert.subject, 'CN=localhost', 'subject should be localhost');
assert.notEqual(cert.issuer, cert.subject, 'should not be self-signed');
// Verify the certificate is signed by the CA
assert.isTrue(cert.verify(caCert.publicKey), 'certificate should be verified by CA public key');
});
it('should include Subject Alternative Name extension', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
const pems = await generate([
{ name: 'commonName', value: 'example.com' }
], {
algorithm: 'sha256',
ca: { key: ca.private, cert: ca.cert }
});
const cert = new crypto.X509Certificate(pems.cert);
assert.include(cert.subjectAltName, 'DNS:example.com', 'should have DNS SAN matching CN');
});
it('should include IP SAN for localhost', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
const pems = await generate([
{ name: 'commonName', value: 'localhost' }
], {
algorithm: 'sha256',
ca: { key: ca.private, cert: ca.cert }
});
const cert = new crypto.X509Certificate(pems.cert);
assert.include(cert.subjectAltName, 'DNS:localhost', 'should have DNS SAN');
assert.include(cert.subjectAltName, 'IP Address:127.0.0.1', 'should have IP SAN for localhost');
});
it('should support different hash algorithms with CA signing', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
// Test sha384
const pems384 = await generate([{ name: 'commonName', value: 'test384.local' }], {
algorithm: 'sha384',
ca: { key: ca.private, cert: ca.cert }
});
const cert384 = new crypto.X509Certificate(pems384.cert);
assert.ok(cert384.publicKey, 'should generate sha384 CA-signed cert');
// Test sha512
const pems512 = await generate([{ name: 'commonName', value: 'test512.local' }], {
algorithm: 'sha512',
ca: { key: ca.private, cert: ca.cert }
});
const cert512 = new crypto.X509Certificate(pems512.cert);
assert.ok(cert512.publicKey, 'should generate sha512 CA-signed cert');
});
it('should respect notAfterDate option with CA signing', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
const notBefore = new Date('2025-01-01T00:00:00Z');
const notAfter = new Date('2025-01-31T00:00:00Z'); // 30 days validity
const pems = await generate([{ name: 'commonName', value: 'short-lived.local' }], {
algorithm: 'sha256',
notBeforeDate: notBefore,
notAfterDate: notAfter,
ca: { key: ca.private, cert: ca.cert }
});
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 unique certificates with same CA', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
const pems1 = await generate([{ name: 'commonName', value: 'test1.local' }], {
algorithm: 'sha256',
ca: { key: ca.private, cert: ca.cert }
});
const pems2 = await generate([{ name: 'commonName', value: 'test2.local' }], {
algorithm: 'sha256',
ca: { key: ca.private, cert: ca.cert }
});
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');
assert.notEqual(pems1.private, pems2.private, 'private keys should be different');
});
it('should work with custom keySize and CA signing', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256',
keySize: 4096
});
const pems = await generate([{ name: 'commonName', value: 'bigkey.local' }], {
algorithm: 'sha256',
keySize: 4096,
ca: { key: ca.private, cert: ca.cert }
});
const privateKey = crypto.createPrivateKey(pems.private);
assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 4096, 'should use custom key size');
const cert = new crypto.X509Certificate(pems.cert);
const caCert = new crypto.X509Certificate(ca.cert);
assert.isTrue(cert.verify(caCert.publicKey), 'certificate should verify with CA');
});
it('should support existing keyPair with CA signing', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
// Generate a key pair first
const keyPair = await generate([{ name: 'commonName', value: 'keypair.local' }], {
algorithm: 'sha256'
});
// Use existing key pair with CA signing
const pems = await generate([{ name: 'commonName', value: 'reused.local' }], {
algorithm: 'sha256',
keyPair: {
privateKey: keyPair.private,
publicKey: keyPair.public
},
ca: { key: ca.private, cert: ca.cert }
});
assert.strictEqual(pems.private, keyPair.private, 'should use provided private key');
assert.strictEqual(pems.public, keyPair.public, 'should use provided public key');
const cert = new crypto.X509Certificate(pems.cert);
const caCert = new crypto.X509Certificate(ca.cert);
assert.isTrue(cert.verify(caCert.publicKey), 'certificate should verify with CA');
});
it('should include proper extended key usage extensions', async function () {
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
algorithm: 'sha256'
});
const pems = await generate([{ name: 'commonName', value: 'server.local' }], {
algorithm: 'sha256',
ca: { key: ca.private, cert: ca.cert }
});
const cert = new crypto.X509Certificate(pems.cert);
// Check extended key usage (OIDs)
// 1.3.6.1.5.5.7.3.1 = serverAuth
// 1.3.6.1.5.5.7.3.2 = clientAuth
assert.include(cert.keyUsage, '1.3.6.1.5.5.7.3.1', 'should have serverAuth extended key usage');
assert.include(cert.keyUsage, '1.3.6.1.5.5.7.3.2', 'should have clientAuth extended key usage');
});
it('should work with PKCS#1 RSA key format', async function () {
// Generate a CA with PKCS#1 format key (like mkcert uses)
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
publicKeyEncoding: { type: 'spki', format: 'pem' }
});
// Create a self-signed CA cert using the PKCS#1 key
const ca = await generate([{ name: 'commonName', value: 'PKCS1 CA' }], {
algorithm: 'sha256'
});
// Now test that we can use a PKCS#1 formatted key as CA
// Convert our generated key to PKCS#1 for testing
const caKeyObject = crypto.createPrivateKey(ca.private);
const pkcs1Key = caKeyObject.export({ type: 'pkcs1', format: 'pem' });
const pems = await generate([{ name: 'commonName', value: 'pkcs1-test.local' }], {
algorithm: 'sha256',
ca: {
key: pkcs1Key,
cert: ca.cert
}
});
const cert = new crypto.X509Certificate(pems.cert);
const caCert = new crypto.X509Certificate(ca.cert);
assert.isTrue(cert.verify(caCert.publicKey), 'should work with PKCS#1 key format');
});
});
+1
-8
{
"permissions": {
"allow": [
"WebSearch",
"WebFetch(domain:github.com)",
"WebFetch(domain:www.npmjs.com)",
"Bash(npm view:*)",
"Bash(npm install:*)",
"Bash(node:*)",
"Bash(openssl pkcs7:*)",
"Bash(npm test:*)"
"Bash(ls:*)"
],

@@ -13,0 +6,0 @@ "deny": [],

@@ -23,8 +23,2 @@ declare enum ASN1Class {

/**
* The number of days before expiration
*
* @default 365 */
days?: number
/**
* The date before which the certificate should not be valid

@@ -36,4 +30,10 @@ *

/**
* The date after which the certificate should not be valid
*
* @default notBeforeDate + 365 days */
notAfterDate?: Date
/**
* the size for the private key in bits
* @default 1024
* @default 2048
*/

@@ -46,3 +46,3 @@ keySize?: number

/**
* The signature algorithm sha256 or sha1
* The signature algorithm: sha256, sha384, sha512 or sha1
* @default "sha1"

@@ -68,5 +68,21 @@ */

* the size for the client private key in bits
* @default 1024
* @default 2048
*/
clientCertificateKeySize?: number
/**
* existing key pair to use instead of generating new keys
*/
keyPair?: {
privateKey: string
publicKey: string
}
/**
* CA certificate and key for signing (if not provided, generates self-signed)
*/
ca?: {
/** CA private key in PEM format */
key: string
/** CA certificate in PEM format */
cert: string
}
}

@@ -79,14 +95,41 @@

fingerprint: string
pkcs7?: string
clientprivate?: string
clientpublic?: string
clientcert?: string
clientpkcs7?: string
}
/**
* Generate a certificate (async only)
*
* @param attrs Certificate attributes
* @param opts Generation options
* @returns Promise that resolves with certificate data
*
* @example
* ```typescript
* // Self-signed certificate
* const pems = await generate();
*
* const pems = await generate([{ name: 'commonName', value: 'example.com' }]);
*
* const pems = await generate(null, {
* keySize: 2048,
* algorithm: 'sha256'
* });
*
* // CA-signed certificate
* const pems = await generate([{ name: 'commonName', value: 'localhost' }], {
* algorithm: 'sha256',
* ca: {
* key: fs.readFileSync('/path/to/ca.key', 'utf8'),
* cert: fs.readFileSync('/path/to/ca.crt', 'utf8')
* }
* });
* ```
*/
export declare function generate(
attrs?: CertificateField[],
opts?: SelfsignedOptions
): GenerateResult
export declare function generate(
attrs?: CertificateField[],
opts?: SelfsignedOptions,
/** Optional callback, if not provided the generation is synchronous */
done?: (err: undefined | Error, result: GenerateResult) => any
): void
): Promise<GenerateResult>
+322
-188

@@ -1,3 +0,7 @@

var forge = require("node-forge");
const { X509CertificateGenerator, X509Certificate, X509ChainBuilder, BasicConstraintsExtension, KeyUsagesExtension, KeyUsageFlags, ExtendedKeyUsageExtension, ExtendedKeyUsage, SubjectAlternativeNameExtension, GeneralName } = require("@peculiar/x509");
const nodeCrypto = require("crypto");
// Use Node.js native webcrypto
const crypto = nodeCrypto.webcrypto;
// a hexString is considered negative if it's most significant bit is 1

@@ -17,232 +21,362 @@ // because serial numbers use ones' complement notation

function getAlgorithm(key) {
function getAlgorithmName(key) {
switch (key) {
case "sha256":
return forge.md.sha256.create();
return "SHA-256";
case 'sha384':
return forge.md.sha384.create();
return "SHA-384";
case 'sha512':
return forge.md.sha512.create();
return "SHA-512";
default:
return forge.md.sha1.create();
return "SHA-1";
}
}
/**
*
* @param {CertificateField[]} attrs Attributes used for subject and issuer.
* @param {object} options
* @param {number} [options.days=365] the number of days before expiration
* @param {number} [options.keySize=2048] the size for the private key in bits
* @param {object} [options.extensions] additional extensions for the certificate
* @param {string} [options.algorithm="sha1"] The signature algorithm sha256 or sha1
* @param {boolean} [options.pkcs7=false] include PKCS#7 as part of the output
* @param {boolean} [options.clientCertificate=false] generate client cert signed by the original key
* @param {string} [options.clientCertificateCN="John Doe jdoe123"] client certificate's common name
* @param {function} [done] Optional callback, if not provided the generation is synchronous
* @returns
*/
exports.generate = function generate(attrs, options, done) {
if (typeof attrs === "function") {
done = attrs;
attrs = undefined;
} else if (typeof options === "function") {
done = options;
options = {};
}
function getSigningAlgorithm(key) {
const hashAlg = getAlgorithmName(key);
return {
name: "RSASSA-PKCS1-v1_5",
hash: hashAlg
};
}
options = options || {};
// Convert attributes from node-forge format to X509 name format
function convertAttributes(attrs) {
const nameMap = {
'commonName': 'CN',
'countryName': 'C',
'ST': 'ST',
'localityName': 'L',
'organizationName': 'O',
'OU': 'OU'
};
var generatePem = function (keyPair) {
var cert = forge.pki.createCertificate();
return attrs.map(attr => {
const key = attr.name || attr.shortName;
const oid = nameMap[key] || key;
return `${oid}=${attr.value}`;
}).join(', ');
}
cert.serialNumber = toPositiveHex(
forge.util.bytesToHex(forge.random.getBytesSync(9))
); // the serial number can be decimal or hex (if preceded by 0x)
// Convert PEM key to CryptoKey
async function importPrivateKey(pemKey, algorithm) {
// Support both PKCS#8 and PKCS#1 (RSA) formats
const pkcs8Match = pemKey.match(/-----BEGIN PRIVATE KEY-----([\s\S]*?)-----END PRIVATE KEY-----/);
const rsaMatch = pemKey.match(/-----BEGIN RSA PRIVATE KEY-----([\s\S]*?)-----END RSA PRIVATE KEY-----/);
cert.validity.notBefore = options.notBeforeDate || new Date();
var notAfter = new Date();
cert.validity.notAfter = notAfter;
cert.validity.notAfter.setDate(notAfter.getDate() + (options.days || 365));
attrs = attrs || [
if (pkcs8Match) {
const pemContents = pkcs8Match[1].replace(/\s/g, '');
const binaryDer = Buffer.from(pemContents, 'base64');
return await crypto.subtle.importKey(
'pkcs8',
binaryDer,
{
name: "commonName",
value: "example.org",
name: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm),
},
true,
['sign']
);
} else if (rsaMatch) {
// PKCS#1 RSA key - need to convert using Node.js crypto
const keyObject = nodeCrypto.createPrivateKey(pemKey);
const pkcs8Pem = keyObject.export({ type: 'pkcs8', format: 'pem' });
const pemContents = pkcs8Pem
.replace(/-----BEGIN PRIVATE KEY-----/, '')
.replace(/-----END PRIVATE KEY-----/, '')
.replace(/\s/g, '');
const binaryDer = Buffer.from(pemContents, 'base64');
return await crypto.subtle.importKey(
'pkcs8',
binaryDer,
{
name: "countryName",
value: "US",
name: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm),
},
{
shortName: "ST",
value: "Virginia",
},
{
name: "localityName",
value: "Blacksburg",
},
{
name: "organizationName",
value: "Test",
},
{
shortName: "OU",
value: "Test",
},
];
true,
['sign']
);
} else {
throw new Error('Unsupported private key format. Expected PKCS#8 or PKCS#1 RSA key.');
}
}
cert.setSubject(attrs);
cert.setIssuer(attrs);
async function importPublicKey(pemKey, algorithm) {
const pemContents = pemKey
.replace(/-----BEGIN PUBLIC KEY-----/, '')
.replace(/-----END PUBLIC KEY-----/, '')
.replace(/\s/g, '');
cert.publicKey = keyPair.publicKey;
const binaryDer = Buffer.from(pemContents, 'base64');
cert.setExtensions(
options.extensions || [
{
name: "basicConstraints",
cA: true,
},
{
name: "keyUsage",
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true,
},
{
name: "subjectAltName",
altNames: [
{
type: 6, // URI
value: "http://example.org/webid#me",
},
],
},
]
);
return await crypto.subtle.importKey(
'spki',
binaryDer,
{
name: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm),
},
true,
['verify']
);
}
cert.sign(keyPair.privateKey, getAlgorithm(options && options.algorithm));
async function generatePemAsync(keyPair, attrs, options, ca) {
const { privateKey, publicKey } = keyPair;
const fingerprint = forge.md.sha1
.create()
.update(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes())
.digest()
.toHex()
.match(/.{2}/g)
.join(":");
// Generate serial number
const serialBytes = crypto.getRandomValues(new Uint8Array(9));
const serialHex = toPositiveHex(Buffer.from(serialBytes).toString('hex'));
var pem = {
private: forge.pki.privateKeyToPem(keyPair.privateKey),
public: forge.pki.publicKeyToPem(keyPair.publicKey),
cert: forge.pki.certificateToPem(cert),
fingerprint: fingerprint,
};
// Set up dates
const notBefore = options.notBeforeDate || new Date();
let notAfter;
if (options.notAfterDate) {
notAfter = options.notAfterDate;
} else {
notAfter = new Date(notBefore);
notAfter.setDate(notAfter.getDate() + 365);
}
if (options && options.pkcs7) {
var p7 = forge.pkcs7.createSignedData();
p7.addCertificate(cert);
pem.pkcs7 = forge.pkcs7.messageToPem(p7);
}
// Default attributes
attrs = attrs || [
{
name: "commonName",
value: "example.org",
},
{
name: "countryName",
value: "US",
},
{
shortName: "ST",
value: "Virginia",
},
{
name: "localityName",
value: "Blacksburg",
},
{
name: "organizationName",
value: "Test",
},
{
shortName: "OU",
value: "Test",
},
];
if (options && options.clientCertificate) {
var clientkeys = forge.pki.rsa.generateKeyPair(
options.clientCertificateKeySize || 2048
);
var clientcert = forge.pki.createCertificate();
clientcert.serialNumber = toPositiveHex(
forge.util.bytesToHex(forge.random.getBytesSync(9))
);
clientcert.validity.notBefore = new Date();
clientcert.validity.notAfter = new Date();
clientcert.validity.notAfter.setFullYear(
clientcert.validity.notBefore.getFullYear() + 1
);
const subjectName = convertAttributes(attrs);
const signingAlg = getSigningAlgorithm(options.algorithm);
var clientAttrs = JSON.parse(JSON.stringify(attrs));
// Extract common name for SAN extension
const commonNameAttr = attrs.find(attr => attr.name === 'commonName' || attr.shortName === 'CN');
const commonName = commonNameAttr ? commonNameAttr.value : 'localhost';
for (var i = 0; i < clientAttrs.length; i++) {
if (clientAttrs[i].name === "commonName") {
if (options.clientCertificateCN)
clientAttrs[i] = {
name: "commonName",
value: options.clientCertificateCN,
};
else
clientAttrs[i] = { name: "commonName", value: "John Doe jdoe123" };
}
}
// Build extensions array
const extensions = [
new BasicConstraintsExtension(false, undefined, true),
new KeyUsagesExtension(KeyUsageFlags.digitalSignature | KeyUsageFlags.keyEncipherment, true),
new ExtendedKeyUsageExtension([ExtendedKeyUsage.serverAuth, ExtendedKeyUsage.clientAuth], false),
new SubjectAlternativeNameExtension([
{ type: 'dns', value: commonName },
...(commonName === 'localhost' ? [{ type: 'ip', value: '127.0.0.1' }] : [])
], false)
];
clientcert.setSubject(clientAttrs);
let cert;
// Set the issuer to the parent key
clientcert.setIssuer(attrs);
if (ca) {
// Generate certificate signed by CA
const caCert = new X509Certificate(ca.cert);
const caPrivateKey = await importPrivateKey(ca.key, options.algorithm || "sha256");
clientcert.publicKey = clientkeys.publicKey;
cert = await X509CertificateGenerator.create({
serialNumber: serialHex,
subject: subjectName,
issuer: caCert.subject,
notBefore: notBefore,
notAfter: notAfter,
signingAlgorithm: signingAlg,
publicKey: publicKey,
signingKey: caPrivateKey,
extensions: extensions
});
} else {
// Generate self-signed certificate
cert = await X509CertificateGenerator.createSelfSigned({
serialNumber: serialHex,
name: subjectName,
notBefore: notBefore,
notAfter: notAfter,
signingAlgorithm: signingAlg,
keys: {
privateKey: privateKey,
publicKey: publicKey
},
extensions: extensions
});
}
// Sign client cert with root cert
clientcert.sign(keyPair.privateKey, getAlgorithm(options && options.algorithm));
// Calculate fingerprint (SHA-1 hash of the certificate)
const certRaw = cert.rawData;
const fingerprintBuffer = await crypto.subtle.digest('SHA-1', certRaw);
const fingerprint = Buffer.from(fingerprintBuffer)
.toString('hex')
.match(/.{2}/g)
.join(':');
pem.clientprivate = forge.pki.privateKeyToPem(clientkeys.privateKey);
pem.clientpublic = forge.pki.publicKeyToPem(clientkeys.publicKey);
pem.clientcert = forge.pki.certificateToPem(clientcert);
// Export keys to PEM
const privateKeyDer = await crypto.subtle.exportKey('pkcs8', privateKey);
const publicKeyDer = await crypto.subtle.exportKey('spki', publicKey);
if (options.pkcs7) {
var clientp7 = forge.pkcs7.createSignedData();
clientp7.addCertificate(clientcert);
pem.clientpkcs7 = forge.pkcs7.messageToPem(clientp7);
const privatePem =
'-----BEGIN PRIVATE KEY-----\n' +
Buffer.from(privateKeyDer).toString('base64').match(/.{1,64}/g).join('\n') +
'\n-----END PRIVATE KEY-----\n';
const publicPem =
'-----BEGIN PUBLIC KEY-----\n' +
Buffer.from(publicKeyDer).toString('base64').match(/.{1,64}/g).join('\n') +
'\n-----END PUBLIC KEY-----\n';
const certPem = cert.toString('pem');
const pem = {
private: privatePem,
public: publicPem,
cert: certPem,
fingerprint: fingerprint,
};
// Client certificate support
if (options && options.clientCertificate) {
const clientKeyPair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: options.clientCertificateKeySize || 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: getAlgorithmName(options.algorithm || "sha1"),
},
true,
["sign", "verify"]
);
const clientSerialBytes = crypto.getRandomValues(new Uint8Array(9));
const clientSerialHex = toPositiveHex(Buffer.from(clientSerialBytes).toString('hex'));
const clientNotBefore = new Date();
const clientNotAfter = new Date();
clientNotAfter.setFullYear(clientNotBefore.getFullYear() + 1);
const clientAttrs = JSON.parse(JSON.stringify(attrs));
for (let i = 0; i < clientAttrs.length; i++) {
if (clientAttrs[i].name === "commonName") {
clientAttrs[i] = {
name: "commonName",
value: options.clientCertificateCN || "John Doe jdoe123"
};
}
}
var caStore = forge.pki.createCaStore();
caStore.addCertificate(cert);
const clientSubjectName = convertAttributes(clientAttrs);
const issuerName = convertAttributes(attrs);
try {
forge.pki.verifyCertificateChain(
caStore,
[cert],
function (vfd, depth, chain) {
if (vfd !== true) {
throw new Error("Certificate could not be verified.");
}
return true;
}
);
} catch (ex) {
throw new Error(ex);
}
// Create client cert signed by root key
const clientCertRaw = await X509CertificateGenerator.create({
serialNumber: clientSerialHex,
subject: clientSubjectName,
issuer: issuerName,
notBefore: clientNotBefore,
notAfter: clientNotAfter,
signingAlgorithm: signingAlg,
publicKey: clientKeyPair.publicKey,
signingKey: privateKey // Sign with root private key
});
return pem;
};
// Export client keys
const clientPrivateKeyDer = await crypto.subtle.exportKey('pkcs8', clientKeyPair.privateKey);
const clientPublicKeyDer = await crypto.subtle.exportKey('spki', clientKeyPair.publicKey);
var keySize = options.keySize || 2048;
pem.clientprivate =
'-----BEGIN PRIVATE KEY-----\n' +
Buffer.from(clientPrivateKeyDer).toString('base64').match(/.{1,64}/g).join('\n') +
'\n-----END PRIVATE KEY-----\n';
if (done) {
// async scenario
return forge.pki.rsa.generateKeyPair(
{ bits: keySize },
function (err, keyPair) {
if (err) {
return done(err);
}
pem.clientpublic =
'-----BEGIN PUBLIC KEY-----\n' +
Buffer.from(clientPublicKeyDer).toString('base64').match(/.{1,64}/g).join('\n') +
'\n-----END PUBLIC KEY-----\n';
try {
return done(null, generatePem(keyPair));
} catch (ex) {
return done(ex);
}
}
pem.clientcert = clientCertRaw.toString('pem');
}
// Verify certificate chain
const x509Cert = new X509Certificate(cert.rawData);
const certificates = [x509Cert];
// If CA-signed, include CA cert in the chain for verification
if (ca) {
const caCert = new X509Certificate(ca.cert);
certificates.push(caCert);
}
const chainBuilder = new X509ChainBuilder({
certificates: certificates
});
const chain = await chainBuilder.build(x509Cert);
if (chain.length === 0) {
throw new Error("Certificate could not be verified.");
}
return pem;
}
/**
* Generate a certificate (async)
*
* @param {CertificateField[]} attrs Attributes used for subject.
* @param {object} options
* @param {number} [options.keySize=2048] the size for the private key in bits
* @param {object} [options.extensions] additional extensions for the certificate
* @param {string} [options.algorithm="sha1"] The signature algorithm sha256, sha384, sha512 or sha1
* @param {Date} [options.notBeforeDate=new Date()] The date before which the certificate should not be valid
* @param {Date} [options.notAfterDate] The date after which the certificate should not be valid (default: notBeforeDate + 365 days)
* @param {boolean} [options.clientCertificate=false] generate client cert signed by the original key
* @param {string} [options.clientCertificateCN="John Doe jdoe123"] client certificate's common name
* @param {object} [options.ca] CA certificate and key for signing (if not provided, generates self-signed)
* @param {string} [options.ca.key] CA private key in PEM format
* @param {string} [options.ca.cert] CA certificate in PEM format
* @returns {Promise<object>} Promise that resolves with certificate data
*/
exports.generate = async function generate(attrs, options) {
attrs = attrs || undefined;
options = options || {};
const keySize = options.keySize || 2048;
let keyPair;
if (options.keyPair) {
// Import existing key pair
keyPair = {
privateKey: await importPrivateKey(options.keyPair.privateKey, options.algorithm || "sha1"),
publicKey: await importPublicKey(options.keyPair.publicKey, options.algorithm || "sha1")
};
} else {
// Generate new key pair
keyPair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: keySize,
publicExponent: new Uint8Array([1, 0, 1]),
hash: getAlgorithmName(options.algorithm || "sha1"),
},
true,
["sign", "verify"]
);
}
var keyPair = options.keyPair
? {
privateKey: forge.pki.privateKeyFromPem(options.keyPair.privateKey),
publicKey: forge.pki.publicKeyFromPem(options.keyPair.publicKey),
}
: forge.pki.rsa.generateKeyPair(keySize);
return generatePem(keyPair);
return await generatePemAsync(keyPair, attrs, options, options.ca);
};
{
"name": "selfsigned",
"version": "4.0.1",
"version": "5.0.0",
"description": "Generate self signed certificates private and public keys",

@@ -18,3 +18,5 @@ "main": "index.js",

"signed",
"certificates"
"certificates",
"x509",
"webcrypto"
],

@@ -36,6 +38,6 @@ "author": "José F. Romaniello <jfromaniello@gmail.com> (http://joseoncode.com)",

"dependencies": {
"node-forge": "^1"
"@peculiar/x509": "^1.14.2",
"pkijs": "^3.3.3"
},
"devDependencies": {
"@types/node-forge": "^1.3.0",
"chai": "^4.3.4",

@@ -45,4 +47,4 @@ "mocha": "^9.1.1"

"engines": {
"node": ">=10"
"node": ">=15.6.0"
}
}
+225
-41

@@ -1,84 +0,268 @@

Generate a self signed x509 certificate from node.js.
# selfsigned
Generate self-signed X.509 certificates using Node.js native crypto.
## Install
```bash
npm install selfsigned
npm install selfsigned
```
## Requirements
- **Node.js >= 15.6.0** (for native WebCrypto support)
## Usage
**Version 5.0 is async-only.** The `generate()` function now returns a Promise.
```js
var selfsigned = require('selfsigned');
var attrs = [{ name: 'commonName', value: 'contoso.com' }];
var pems = selfsigned.generate(attrs, { days: 365 });
console.log(pems)
```
const selfsigned = require('selfsigned');
#### Async
```js
selfsigned.generate(attrs, { days: 365 }, function (err, pems) {
console.log(pems)
});
const attrs = [{ name: 'commonName', value: 'contoso.com' }];
const pems = await selfsigned.generate(attrs);
console.log(pems);
```
Will return the following like this:
### Output
```js
{
private: '-----BEGIN RSA PRIVATE KEY-----\r\nMIICXAIBAAKBgQCBFMXMYS/+RZz6+qzv+xeqXPdjw4YKZC4y3dPhSwgEwkecrCTX\r\nsR6boue+1MjIqPqWggXZnotIGldfEN0kn0Jbh2vMTrTx6YwqQ8tceBPoyuuqcYBO\r\nOONAcKOB3MLnZbyOgVtbyT3j68JE5V/lx6LhpIKAgY0m5WIuaKrW6mvLXQIDAQAB\r\nAoGAU6ODGxAqSecPdayyG/ml9vSwNAuAMgGB0eHcpZG5i2PbhRAh+0TAIXaoFQXJ\r\naAPeA2ISqlTJyRmQXYAO2uj61FzeyDzYCf0z3+yZEVz3cO7jB5Pl6iBvzbxWuuuA\r\ncbJtWLhWtW5/jioc8F0EAzZ+lkC/XuVJdwKHDmwt2qvJO+ECQQD+dvo1g3Sz9xGw\r\n21n+fDG5i4128+Qh+JPgh5AeLuXSofc1HMHaOXcC6Wu/Cloh7QAD934b7W0A7VoD\r\ndLd/JLyFAkEAgdwjryyvdhy69e516IrPB3b+m4rggtntBlZREMrk9tOzeIucVO3W\r\ntKI3FHm6JebN2gVcG+rZ+FaDPo+ifJkW+QJBAPojrMwEACmUevB2f9246gxx0UsY\r\nbq6yM3No71OsWEEY8/Bi53CEQqg7Gq5+F6H33qcHmBEN8LQTngN9rY+vZh0CQBg0\r\nqJImii5B/LeK03+dICoMDDmCEYdSh9P+ku3GZBd+Lp3xqBpMmxDgi9PNPN2DwCs7\r\nhIfPpwGbXqtyqp7/CkECQB4OdY+2FbCciI473eQkTu310RMf8jElU63iwnx4R/XN\r\n/mgqN589OfF4SS0U/MoRzYk9jF9IAJN1Mi/571T+nw4=\r\n-----END RSA PRIVATE KEY-----\r\n',
public: '-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCBFMXMYS/+RZz6+qzv+xeqXPdj\r\nw4YKZC4y3dPhSwgEwkecrCTXsR6boue+1MjIqPqWggXZnotIGldfEN0kn0Jbh2vM\r\nTrTx6YwqQ8tceBPoyuuqcYBOOONAcKOB3MLnZbyOgVtbyT3j68JE5V/lx6LhpIKA\r\ngY0m5WIuaKrW6mvLXQIDAQAB\r\n-----END PUBLIC KEY-----\r\n',
cert: '-----BEGIN CERTIFICATE-----\r\nMIICjTCCAfagAwIBAgIBATANBgkqhkiG9w0BAQUFADBpMRQwEgYDVQQDEwtleGFt\r\ncGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQH\r\nEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MB4XDTEz\r\nMDgxMzA1NDAyN1oXDTE0MDgxMzA1NDAyN1owaTEUMBIGA1UEAxMLZXhhbXBsZS5v\r\ncmcxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxh\r\nY2tzYnVyZzENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDCBnzANBgkqhkiG\r\n9w0BAQEFAAOBjQAwgYkCgYEAgRTFzGEv/kWc+vqs7/sXqlz3Y8OGCmQuMt3T4UsI\r\nBMJHnKwk17Eem6LnvtTIyKj6loIF2Z6LSBpXXxDdJJ9CW4drzE608emMKkPLXHgT\r\n6MrrqnGATjjjQHCjgdzC52W8joFbW8k94+vCROVf5cei4aSCgIGNJuViLmiq1upr\r\ny10CAwEAAaNFMEMwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAvQwJgYDVR0RBB8w\r\nHYYbaHR0cDovL2V4YW1wbGUub3JnL3dlYmlkI21lMA0GCSqGSIb3DQEBBQUAA4GB\r\nAC9hGQlDh8anNo1YDJdG2mYqOQ5uybJV++kixblGaOkoDROPsWepUpL6kMDUtbAM\r\n4uXTyFkvlUQSaQkhNgOY5w/BRIAkCIu6u4D4XcjlCdwFq6vcKMEuWTHMAlBWFla3\r\nXJZAPO10PHuDen7JeMOUf1Re7lRFtwfRGAvVYmrvYFKv\r\n-----END CERTIFICATE-----\r\n'
private: '-----BEGIN PRIVATE KEY-----\n...',
public: '-----BEGIN PUBLIC KEY-----\n...',
cert: '-----BEGIN CERTIFICATE-----\n...',
fingerprint: 'XX:XX:XX:...'
}
```
## Attributes
for attributes, please refer to: https://github.com/digitalbazaar/forge/blob/master/lib/x509.js
## Options
```js
var pems = selfsigned.generate(null, {
keySize: 2048, // the size for the private key in bits (default: 1024)
days: 30, // how long till expiry of the signed certificate (default: 365)
notBeforeDate: new Date(), // The date before which the certificate should not be valid (default: now)
const pems = await selfsigned.generate(null, {
keySize: 2048, // the size for the private key in bits (default: 2048)
notBeforeDate: new Date(), // start of certificate validity (default: now)
notAfterDate: new Date('2026-01-01'), // end of certificate validity (default: notBeforeDate + 365 days)
algorithm: 'sha256', // sign the certificate with specified algorithm (default: 'sha1')
extensions: [{ name: 'basicConstraints', cA: true }], // certificate extensions array
pkcs7: true, // include PKCS#7 as part of the output (default: false)
clientCertificate: true, // generate client cert signed by the original key (default: false)
clientCertificateCN: 'jdoe' // client certificate's common name (default: 'John Doe jdoe123')
clientCertificateCN: 'jdoe', // client certificate's common name (default: 'John Doe jdoe123')
clientCertificateKeySize: 2048, // the size for the client private key in bits (default: 2048)
ca: { key: '...', cert: '...' } // CA key and cert for signing (default: self-signed)
});
```
> You can avoid key pair generation specifying your own keys (`{ keyPair: { publicKey: '-----BEGIN PUBLIC KEY-----...', privateKey: '-----BEGIN RSA PRIVATE KEY-----...' }`)
### Setting Custom Validity Period
### Generate Client Certificates
Use `notBeforeDate` and `notAfterDate` to control certificate validity:
If you are in an environment where servers require client certificates, you can generate client keys signed by the original (server) key.
```js
// Using date-fns
const { addDays, addYears } = require('date-fns');
const pems = await selfsigned.generate(null, {
notBeforeDate: new Date(),
notAfterDate: addDays(new Date(), 30) // Valid for 30 days
});
// Or with vanilla JS
const notBefore = new Date();
const notAfter = new Date(notBefore);
notAfter.setFullYear(notAfter.getFullYear() + 2); // Valid for 2 years
const pems = await selfsigned.generate(null, {
notBeforeDate: notBefore,
notAfterDate: notAfter
});
```
### Supported Algorithms
- `sha1` (default)
- `sha256`
- `sha384`
- `sha512`
### Using Your Own Keys
You can avoid key pair generation by specifying your own keys:
```js
var pems = selfsigned.generate(null, { clientCertificate: true });
console.log(pems)
const pems = await selfsigned.generate(null, {
keyPair: {
publicKey: '-----BEGIN PUBLIC KEY-----...',
privateKey: '-----BEGIN PRIVATE KEY-----...'
}
});
```
Will return the following like this:
### Signing with a CA
You can generate certificates signed by an existing Certificate Authority instead of self-signed certificates. This is useful for development environments where you want browsers to trust your certificates.
```js
{ private: '-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDLg/kS4dCPVu96sbK6MQuUPmhqnF8SeBXVHH18h+0BTj7HqnrA\r\nA75hNVIiSLTChvpzQ0qi2Ju7O2ESUOdx7cvGiftGuZLiI8uL2HVlYuX+wQTIoRHx\r\n9nxv56TIiqnPg5d05vSTLXoiJg5uac3a6+4vnhhTo0XRRXVVboZsfNpuGQIDAQAB\r\nAoGAfhCd9QhUPLZJWeNBJvzCg221GHUMn1Arlfsz8DPyp+BkGyKLLu4iu+xfmEUZ\r\nU3ZxJX0FeqJatTwvAT2EYJpAovx+F37PWFTLAS6T57WI1O5Lj1pTIKVkLrasNQgF\r\nl6qFD3cvEtCZve4LiwDoJ52FO2OtcDcMJ0r2oqbCXSDIlAECQQDnkkxKcTejBZGH\r\nyYEXG9hAznnEZ63LLzlHHF2cIPfxT+9826Wm0IzBxn8Wr4hcAbNx3bVKgsU9p7xA\r\nfKnSqObhAkEA4PwCjPQqxFpiYUmNt7htb8nCEvUDD/QSDyxAH/uJzfr6gOJOD5nT\r\n5gZYblC+CCMDkgDUpro6oATNyeRNoU3GOQJBANdaW26DWZ1WqV9hCpcGAxdJrT30\r\nuVASq66w93Ehy9LzZqFz1tqKacwvH7NmLGZ8AngrGdSgRnOvEMfb50aMYqECQDcG\r\nzCTnbzJZHOjIkaXWsMV/pjz2ugoD2wrk+sYXwoujj/NH5mnAaOhAsw5AJ0pcLfpe\r\nw6QHtmD+68ouUaJbIFkCQQDeu0AXAp6Kbk6570i2DpGUSnkRdGCGS+3ekqqJUpE7\r\nfVUSx1nCF1sPD0p+pO8Rj3i87iI4MlblQRm/wVkrkjiR\r\n-----END RSA PRIVATE KEY-----\r\n',
public: '-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLg/kS4dCPVu96sbK6MQuUPmhq\r\nnF8SeBXVHH18h+0BTj7HqnrAA75hNVIiSLTChvpzQ0qi2Ju7O2ESUOdx7cvGiftG\r\nuZLiI8uL2HVlYuX+wQTIoRHx9nxv56TIiqnPg5d05vSTLXoiJg5uac3a6+4vnhhT\r\no0XRRXVVboZsfNpuGQIDAQAB\r\n-----END PUBLIC KEY-----\r\n',
cert: '-----BEGIN CERTIFICATE-----\r\nMIIClTCCAf6gAwIBAgIJdMZqoEeGMVYKMA0GCSqGSIb3DQEBBQUAMGkxFDASBgNV\r\nBAMTC2V4YW1wbGUub3JnMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWEx\r\nEzARBgNVBAcTCkJsYWNrc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRl\r\nc3QwHhcNMTUxMDI5MTMwNjA1WhcNMTYxMDI4MTMwNjA1WjBpMRQwEgYDVQQDEwtl\r\neGFtcGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD\r\nVQQHEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MIGf\r\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLg/kS4dCPVu96sbK6MQuUPmhqnF8S\r\neBXVHH18h+0BTj7HqnrAA75hNVIiSLTChvpzQ0qi2Ju7O2ESUOdx7cvGiftGuZLi\r\nI8uL2HVlYuX+wQTIoRHx9nxv56TIiqnPg5d05vSTLXoiJg5uac3a6+4vnhhTo0XR\r\nRXVVboZsfNpuGQIDAQABo0UwQzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIC9DAm\r\nBgNVHREEHzAdhhtodHRwOi8vZXhhbXBsZS5vcmcvd2ViaWQjbWUwDQYJKoZIhvcN\r\nAQEFBQADgYEAj1Yyyb0R9KRFjIWNFi6RErB/riWylW4CdOK1hOyJZ+VRBWeYLKfX\r\ni///V+tqRvLlYY5x5DnrjXbDjBy0CZuN/J772/Srgp7Nl5cn92zynMJK1q4MEEs3\r\nAE/FO85R0HbGEp+IrwUwDOLR6omBFVdh1EUOTcQU2jLZNbWvLDiWbDo=\r\n-----END CERTIFICATE-----\r\n',
clientprivate: '-----BEGIN RSA PRIVATE KEY-----\r\nMIICWwIBAAKBgQDjR5FrrdZ1jirqkx3KMPnGjrcObj/vmztWTEZ1kX6gTskQugJU\r\noxktzwDZza4jYODC6Ud2jouFLWeAi5BDSAeLwAQb951qVD9zVsmQ+63V/mvSJUoj\r\nigwj7YjcxyReJ17F0YgjceqrkZaPM8YRo8h1fj1JdPc4ZOUgA5ASZ0h2ewIDAQAB\r\nAoGAfB5DbjibG8ut6Di7VgX1AdhCY+EVjXaKqxAwklgIfOdJqpbKWwpO39NiNY+7\r\nf5qSZB8dZcNmsi4fjfWprPSTGVkk1Qp2uibtFS4MhbLEeyy4cgZfMIBQY+HD0Asf\r\n1NU7WTY5QfzgH3HAKuWpUEWdar/jE+hDPA+wnsMg+TgGARECQQDzlc+5WA9JsG9f\r\nwNRzhMGRxDP4QLmL0iLWupF4BMP/k4OLMjDtzWl725WJ4FjCzML7mSmkWWe/P8f5\r\nwrbR+e8lAkEA7t0CEsiIw8BE55YMuGIz5xI0QDnuwNWmCEmq6+ZziW3L+EuAr1S4\r\nDORqBYm5DuRvBWkWE9Sld0a8vNqWh58tHwJAP1ZYEhicuQuAmkRYucTuVEnRPZ8O\r\n4BV+65jNlIigskcYMEyXvm3oHMWnJ5fHXLfDh4p28n4w5ODfzcjcotK7ZQJAE7bX\r\n8fbtGsLmrPp8aEdqozqkZ1ygsPexMWPrIHcvt/sA56hLoazrV90ORxC73lfKNfcb\r\nZF2bnoGPGEMuQ1lG3wJAPnHysm3DgbSHZQiXWMjF4YDRRV2AeOqX1fmlSeMErwdj\r\ncwIs+ikIBnOwUOh6liJ7yK1YnckDTZTOfUDyG+vdFQ==\r\n-----END RSA PRIVATE KEY-----\r\n',
clientpublic: '-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjR5FrrdZ1jirqkx3KMPnGjrcO\r\nbj/vmztWTEZ1kX6gTskQugJUoxktzwDZza4jYODC6Ud2jouFLWeAi5BDSAeLwAQb\r\n951qVD9zVsmQ+63V/mvSJUojigwj7YjcxyReJ17F0YgjceqrkZaPM8YRo8h1fj1J\r\ndPc4ZOUgA5ASZ0h2ewIDAQAB\r\n-----END PUBLIC KEY-----\r\n',
clientcert: '-----BEGIN CERTIFICATE-----\r\nMIICSzCCAbSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBpMRQwEgYDVQQDEwtleGFt\r\ncGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQH\r\nEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MB4XDTE1\r\nMTAyOTEzMDYwNVoXDTE2MTAyOTEzMDYwNVowbjEZMBcGA1UEAxMQSm9obiBEb2Ug\r\namRvZTEyMzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQH\r\nEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MIGfMA0G\r\nCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjR5FrrdZ1jirqkx3KMPnGjrcObj/vmztW\r\nTEZ1kX6gTskQugJUoxktzwDZza4jYODC6Ud2jouFLWeAi5BDSAeLwAQb951qVD9z\r\nVsmQ+63V/mvSJUojigwj7YjcxyReJ17F0YgjceqrkZaPM8YRo8h1fj1JdPc4ZOUg\r\nA5ASZ0h2ewIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACOUglBxJ80jzR3DSSMrgRav\r\n7deKUPShEPC3tbVrc3LHPGpCEJUC309aK2mbMwz2jX78tr/ezePELKbyRggUvVgN\r\nB0XdIQkpR9X4mPdtFYkMiWKNVYKd79r0kolprgFPryhT3jsICIOnwE1Ur23Q+Fk2\r\nnizRS0HY4Q25JLCmsWWy\r\n-----END CERTIFICATE-----\r\n' }
const fs = require('fs');
const selfsigned = require('selfsigned');
const pems = await selfsigned.generate([
{ name: 'commonName', value: 'localhost' }
], {
algorithm: 'sha256',
ca: {
key: fs.readFileSync('/path/to/ca.key', 'utf8'),
cert: fs.readFileSync('/path/to/ca.crt', 'utf8')
}
});
```
To override the default client CN of `john doe jdoe123`, add another option for `clientCertificateCN`:
The generated certificate will be signed by the provided CA and will include:
- Subject Alternative Name (SAN) extension with DNS name matching the commonName
- For `localhost`, an additional IP SAN for `127.0.0.1`
- Key Usage: digitalSignature, keyEncipherment
- Extended Key Usage: serverAuth, clientAuth
#### Using with mkcert
[mkcert](https://github.com/FiloSottile/mkcert) is a simple tool for making locally-trusted development certificates. Combining it with `selfsigned` provides an excellent developer experience:
- **No certificate files to manage** - generate trusted certificates on-the-fly at server startup
- **No git-ignored cert files** - nothing to store, share, or accidentally commit
- **Browsers trust the certificates automatically** - no security warnings during development
```js
var pems = selfsigned.generate(null, { clientCertificate: true, clientCertificateCN: 'FooBar' });
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const selfsigned = require('selfsigned');
// Get mkcert's CA (requires: brew install mkcert && mkcert -install)
const caroot = execSync('mkcert -CAROOT', { encoding: 'utf8' }).trim();
const pems = await selfsigned.generate([
{ name: 'commonName', value: 'localhost' }
], {
algorithm: 'sha256',
ca: {
key: fs.readFileSync(path.join(caroot, 'rootCA-key.pem'), 'utf8'),
cert: fs.readFileSync(path.join(caroot, 'rootCA.pem'), 'utf8')
}
});
// Start server with browser-trusted certificate - no files written to disk
https.createServer({ key: pems.private, cert: pems.cert }, app).listen(443);
```
See [examples/https-server-mkcert.js](examples/https-server-mkcert.js) for a complete working example.
## Attributes
Attributes follow the X.509 standard:
```js
const attrs = [
{ name: 'commonName', value: 'example.org' },
{ name: 'countryName', value: 'US' },
{ shortName: 'ST', value: 'Virginia' },
{ name: 'localityName', value: 'Blacksburg' },
{ name: 'organizationName', value: 'Test' },
{ shortName: 'OU', value: 'Test' }
];
```
## Generate Client Certificates
For environments where servers require client certificates, you can generate client keys signed by the original (server) key:
```js
const pems = await selfsigned.generate(null, { clientCertificate: true });
console.log(pems);
```
Output includes additional client certificate fields:
```js
{
private: '-----BEGIN PRIVATE KEY-----\n...',
public: '-----BEGIN PUBLIC KEY-----\n...',
cert: '-----BEGIN CERTIFICATE-----\n...',
fingerprint: 'XX:XX:XX:...',
clientprivate: '-----BEGIN PRIVATE KEY-----\n...',
clientpublic: '-----BEGIN PUBLIC KEY-----\n...',
clientcert: '-----BEGIN CERTIFICATE-----\n...'
}
```
To override the default client CN of `John Doe jdoe123`:
```js
const pems = await selfsigned.generate(null, {
clientCertificate: true,
clientCertificateCN: 'FooBar'
});
```
## PKCS#7 Support
PKCS#7 formatting is available through a separate module for better tree-shaking:
```js
const selfsigned = require('selfsigned');
const { createPkcs7 } = require('selfsigned/pkcs7');
const pems = await selfsigned.generate(attrs);
const pkcs7 = createPkcs7(pems.cert);
console.log(pkcs7); // PKCS#7 formatted certificate
```
You can also create PKCS#7 for client certificates:
```js
const pems = await selfsigned.generate(null, { clientCertificate: true });
const clientPkcs7 = createPkcs7(pems.clientcert);
```
## Migration from v4.x
Version 5.0 introduces breaking changes:
### Breaking Changes
1. **Async-only API**: The `generate()` function is now async and returns a Promise. Synchronous generation is no longer supported.
2. **No callback support**: Callbacks have been removed. Use `async`/`await` or `.then()`.
3. **Minimum Node.js version**: Now requires Node.js >= 15.6.0 (was >= 10).
4. **Dependencies**: Replaced `node-forge` with `@peculiar/x509` and `pkijs` (66% smaller bundle size).
5. **`days` option removed**: Use `notAfterDate` instead. Default validity is 365 days from `notBeforeDate`.
### Migration Examples
**Old (v4.x):**
```js
// Sync
const pems = selfsigned.generate(attrs, { days: 365 });
// Callback
selfsigned.generate(attrs, { days: 365 }, function(err, pems) {
if (err) throw err;
console.log(pems);
});
```
**New (v5.x):**
```js
// Async/await (default 365 days validity)
const pems = await selfsigned.generate(attrs);
// Custom validity with notAfterDate
const notAfter = new Date();
notAfter.setDate(notAfter.getDate() + 30); // 30 days
const pems = await selfsigned.generate(attrs, { notAfterDate: notAfter });
// Or with .then()
selfsigned.generate(attrs)
.then(pems => console.log(pems))
.catch(err => console.error(err));
```
## License
MIT
var { assert } = require('chai');
var forge = require('node-forge');
var fs = require('fs');
var exec = require('child_process').exec;
var { promisify } = require('util');
var exec = promisify(require('child_process').exec);
var crypto = require('crypto');

@@ -9,5 +10,6 @@ describe('generate', function () {

var generate = require('../index').generate;
var { createPkcs7 } = require('../pkcs7');
it('should work without attrs/options', function (done) {
var pems = generate();
it('should work without attrs/options', async function () {
var pems = await generate();
assert.ok(!!pems.private, 'has a private key');

@@ -22,9 +24,9 @@ assert.ok(!!pems.fingerprint, 'has fingerprint');

var caStore = forge.pki.createCaStore();
caStore.addCertificate(pems.cert);
done();
// 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', function (done) {
var pems = generate(null, {clientCertificate: true});
it('should generate client cert', async function () {
var pems = await generate(null, {clientCertificate: true});

@@ -34,9 +36,9 @@ assert.ok(!!pems.clientcert, 'should include a client cert when requested');

assert.ok(!!pems.clientpublic, 'should include a client public key when requested');
done();
});
it('should include pkcs7', function (done) {
var pems = generate([{ name: 'commonName', value: 'contoso.com' }], {pkcs7: true});
it('should include pkcs7', async function () {
var pems = await generate([{ name: 'commonName', value: 'contoso.com' }]);
var pkcs7 = createPkcs7(pems.cert);
assert.ok(!!pems.pkcs7, 'has a pkcs7');
assert.ok(!!pkcs7, 'has a pkcs7');

@@ -47,146 +49,253 @@ try {

fs.writeFileSync('/tmp/tmp.pkcs7', pems.pkcs7);
exec('openssl pkcs7 -print_certs -in /tmp/tmp.pkcs7', function (err, stdout, stderr) {
if (err) {
return done(err);
}
fs.writeFileSync('/tmp/tmp.pkcs7', pkcs7);
const errorMessage = stderr.toString();
if (errorMessage.length) {
return done(new Error(errorMessage));
}
const { stdout, stderr } = await exec('openssl pkcs7 -print_certs -in /tmp/tmp.pkcs7');
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);
assert.strictEqual(
pems.cert,
cert.join('\r\n') + '\r\n'
);
if (stderr && stderr.length) {
throw new Error(stderr);
}
done();
});
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', function (done) {
var pems_sha1 = generate(null, { algorithm: 'sha1' });
assert.ok(forge.pki.certificateFromPem(pems_sha1.cert).siginfo.algorithmOid === forge.pki.oids['sha1WithRSAEncryption'], 'can generate sha1 certs');
done();
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', function (done) {
var pems_sha256 = generate(null, { algorithm: 'sha256' });
assert.ok(forge.pki.certificateFromPem(pems_sha256.cert).siginfo.algorithmOid === forge.pki.oids['sha256WithRSAEncryption'], 'can generate sha256 certs');
done();
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', function (done) {
var pems = generate();
var privateKey = forge.pki.privateKeyFromPem(pems.private);
assert.strictEqual(privateKey.n.bitLength(), 2048, 'default key size should be 2048 bits');
done();
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', function (done) {
var pems = generate(null, {clientCertificate: true});
var clientPrivateKey = forge.pki.privateKeyFromPem(pems.clientprivate);
assert.strictEqual(clientPrivateKey.n.bitLength(), 2048, 'default client key size should be 2048 bits');
done();
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');
});
describe('with callback', function () {
it('should work without attrs/options', function (done) {
generate(function (err, pems) {
if (err) done(err);
assert.ok(!!pems.private, 'has a private key');
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');
done();
});
});
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 generate client cert', function (done) {
generate(null, {clientCertificate: true}, function (err, pems) {
if (err) done(err);
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');
done();
});
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 include pkcs7', function (done) {
generate([{ name: 'commonName', value: 'contoso.com' }], {pkcs7: true}, function (err, pems) {
if (err) done(err);
assert.ok(!!pems.pkcs7, 'has a pkcs7');
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');
});
try {
fs.unlinkSync('/tmp/tmp.pkcs7');
} catch (er) {}
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');
});
fs.writeFileSync('/tmp/tmp.pkcs7', pems.pkcs7);
exec('openssl pkcs7 -print_certs -in /tmp/tmp.pkcs7', function (err, stdout, stderr) {
if (err) {
return done(err);
}
it('should default to 365 days validity', async function () {
var pems = await generate();
const cert = new crypto.X509Certificate(pems.cert);
const errorMessage = stderr.toString();
if (errorMessage.length) {
return done(new Error(errorMessage));
}
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));
const expected = stdout.toString();
let [ subjectLine,issuerLine, ...cert ] = expected.split(/\r?\n/).filter(c => c);
assert.match(subjectLine, /subject=\/?CN\s?=\s?contoso.com/i);
assert.match(issuerLine, /issuer=\/?CN\s?=\s?contoso.com/i);
assert.strictEqual(
pems.cert,
cert.join('\r\n') + '\r\n'
);
assert.approximately(diffDays, 365, 1, 'certificate should default to 365 days validity');
});
done();
});
});
});
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);
it('should support sha1 algorithm', function (done) {
generate(null, { algorithm: 'sha1' }, function (err, pems_sha1) {
if (err) done(err);
assert.ok(forge.pki.certificateFromPem(pems_sha1.cert).siginfo.algorithmOid === forge.pki.oids['sha1WithRSAEncryption'], 'can generate sha1 certs');
done();
});
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', async function () {
var pems = await generate(null, {
clientCertificate: true,
clientCertificateCN: 'Custom User CN'
});
it('should support sha256 algorithm', function (done) {
generate(null, { algorithm: 'sha256' }, function (err, pems_sha256) {
if (err) done(err);
assert.ok(forge.pki.certificateFromPem(pems_sha256.cert).siginfo.algorithmOid === forge.pki.oids['sha256WithRSAEncryption'], 'can generate sha256 certs');
done();
});
const clientCert = new crypto.X509Certificate(pems.clientcert);
assert.include(clientCert.subject, 'CN=Custom User CN', 'should use custom client CN');
});
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
}
});
it('should default to 2048 bit keysize', function (done) {
generate(function (err, pems) {
if (err) done(err);
var privateKey = forge.pki.privateKeyFromPem(pems.private);
assert.strictEqual(privateKey.n.bitLength(), 2048, 'default key size should be 2048 bits');
done();
});
// 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
});
it('should default to 2048 bit keysize for client certificate', function (done) {
generate(null, {clientCertificate: true}, function (err, pems) {
if (err) done(err);
var clientPrivateKey = forge.pki.privateKeyFromPem(pems.clientprivate);
assert.strictEqual(clientPrivateKey.n.bitLength(), 2048, 'default client key size should be 2048 bits');
done();
});
});
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');
});
});