🎩 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
5.2.0
to
5.4.0
+142
test/ec-keys.js
var { assert } = require('chai');
var crypto = require('crypto');
describe('EC keys', function () {
var generate = require('../index').generate;
it('should generate EC certificate with P-256 curve (default)', async function () {
var pems = await generate(null, { keyType: 'ec' });
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 privateKey = crypto.createPrivateKey(pems.private);
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'should be EC key');
assert.strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'prime256v1', 'should use P-256 curve');
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.subject, 'cert has a subject');
});
it('should generate EC certificate with P-384 curve', async function () {
var pems = await generate(null, { keyType: 'ec', curve: 'P-384' });
const privateKey = crypto.createPrivateKey(pems.private);
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'should be EC key');
assert.strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'secp384r1', 'should use P-384 curve');
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.publicKey, 'can generate P-384 EC certs');
});
it('should generate EC certificate with P-521 curve', async function () {
var pems = await generate(null, { keyType: 'ec', curve: 'P-521' });
const privateKey = crypto.createPrivateKey(pems.private);
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'should be EC key');
assert.strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'secp521r1', 'should use P-521 curve');
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.publicKey, 'can generate P-521 EC certs');
});
it('should generate valid EC key pair that work together', async function () {
var pems = await generate(null, { keyType: 'ec', curve: 'P-256' });
const testData = 'Hello, World!';
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, 'EC public key should verify signature from EC private key');
});
it('should support EC with sha256 algorithm', async function () {
var pems = await generate(null, { keyType: 'ec', algorithm: 'sha256' });
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.publicKey, 'can generate EC cert with sha256');
});
it('should support EC with sha384 algorithm', async function () {
var pems = await generate(null, { keyType: 'ec', curve: 'P-384', algorithm: 'sha384' });
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.publicKey, 'can generate EC cert with sha384');
});
it('should support EC with sha512 algorithm', async function () {
var pems = await generate(null, { keyType: 'ec', curve: 'P-521', algorithm: 'sha512' });
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.publicKey, 'can generate EC cert with sha512');
});
it('should generate EC client certificate', async function () {
var pems = await generate(null, { keyType: 'ec', clientCertificate: true });
assert.ok(!!pems.clientcert, 'should include a client cert');
assert.ok(!!pems.clientprivate, 'should include a client private key');
assert.ok(!!pems.clientpublic, 'should include a client public key');
const clientPrivateKey = crypto.createPrivateKey(pems.clientprivate);
assert.strictEqual(clientPrivateKey.asymmetricKeyType, 'ec', 'client key should be EC');
});
it('should support passphrase with EC keys', async function () {
const passphrase = 'ec-secret-passphrase';
var pems = await generate(null, { keyType: 'ec', passphrase: passphrase });
assert.include(pems.private, 'ENCRYPTED', 'EC private key should be encrypted');
const privateKey = crypto.createPrivateKey({
key: pems.private,
passphrase: passphrase
});
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'decrypted key should be EC');
});
it('should support using existing EC keyPair', async function () {
const firstPems = await generate(null, { keyType: 'ec', curve: 'P-256' });
const secondPems = await generate(null, {
keyType: 'ec',
curve: 'P-256',
keyPair: {
privateKey: firstPems.private,
publicKey: firstPems.public
}
});
assert.strictEqual(firstPems.private, secondPems.private, 'should use provided EC private key');
assert.strictEqual(firstPems.public, secondPems.public, 'should use provided EC public key');
});
it('should support custom attributes with EC', async function () {
const attrs = [
{ name: 'commonName', value: 'ec-test.example.com' },
{ name: 'countryName', value: 'US' },
{ name: 'organizationName', value: 'EC Test Corp' }
];
var pems = await generate(attrs, { keyType: 'ec' });
const cert = new crypto.X509Certificate(pems.cert);
assert.include(cert.subject, 'CN=ec-test.example.com', 'should include custom CN');
assert.include(cert.subject, 'O=EC Test Corp', 'should include custom organization');
});
});
+4
-1

@@ -6,3 +6,6 @@ {

"Bash(npm test)",
"Bash(gh issue view:*)"
"Bash(gh issue view:*)",
"Bash(npx mocha:*)",
"Bash(gh issue comment:*)",
"Bash(gh issue close:*)"
],

@@ -9,0 +12,0 @@ "deny": [],

+145
-4

@@ -21,2 +21,117 @@ declare enum ASN1Class {

/**
* Subject Alternative Name entry types:
* - 1: email (rfc822Name)
* - 2: DNS name
* - 6: URI
* - 7: IP address
*/
declare interface SubjectAltNameEntry {
/**
* Type of the alternative name:
* - 1: email (rfc822Name)
* - 2: DNS name
* - 6: URI
* - 7: IP address
*/
type: 1 | 2 | 6 | 7;
/** Value for types 1, 2, 6 (email, DNS, URI) */
value?: string;
/** IP address for type 7 (IPv4 or IPv6) */
ip?: string;
}
declare interface BasicConstraintsExtension {
name: 'basicConstraints';
/** Is this a CA certificate? */
cA?: boolean;
/** Maximum depth of valid certificate chain */
pathLenConstraint?: number;
/** Mark extension as critical */
critical?: boolean;
}
declare interface KeyUsageExtension {
name: 'keyUsage';
digitalSignature?: boolean;
nonRepudiation?: boolean;
/** Also known as contentCommitment */
contentCommitment?: boolean;
keyEncipherment?: boolean;
dataEncipherment?: boolean;
keyAgreement?: boolean;
/** For CA certificates */
keyCertSign?: boolean;
/** For CA certificates */
cRLSign?: boolean;
encipherOnly?: boolean;
decipherOnly?: boolean;
/** Mark extension as critical */
critical?: boolean;
}
declare interface ExtKeyUsageExtension {
name: 'extKeyUsage';
/** TLS server authentication */
serverAuth?: boolean;
/** TLS client authentication */
clientAuth?: boolean;
codeSigning?: boolean;
emailProtection?: boolean;
timeStamping?: boolean;
/** Mark extension as critical */
critical?: boolean;
}
declare interface SubjectAltNameExtension {
name: 'subjectAltName';
altNames: SubjectAltNameEntry[];
/** Mark extension as critical */
critical?: boolean;
}
declare type CertificateExtension =
| BasicConstraintsExtension
| KeyUsageExtension
| ExtKeyUsageExtension
| SubjectAltNameExtension;
declare interface ClientCertificateOptions {
/**
* Key size for the client certificate in bits (RSA only)
* @default 2048
*/
keySize?: number
/**
* Key type for client certificate
* @default inherits from main keyType
*/
keyType?: 'rsa' | 'ec'
/**
* Elliptic curve for client certificate (EC only)
* @default "P-256"
*/
curve?: 'P-256' | 'P-384' | 'P-521'
/**
* Signature algorithm for client certificate
* @default inherits from main algorithm or "sha1"
*/
algorithm?: string
/**
* Client certificate's common name
* @default "John Doe jdoe123"
*/
cn?: string
/**
* The date before which the client certificate should not be valid
* @default now
*/
notBeforeDate?: Date
/**
* The date after which the client certificate should not be valid
* @default notBeforeDate + 1 year
*/
notAfterDate?: Date
}
declare interface SelfsignedOptions {

@@ -36,3 +151,8 @@ /**

/**
* the size for the private key in bits
* Key type: "rsa" or "ec" (elliptic curve)
* @default "rsa"
*/
keyType?: 'rsa' | 'ec'
/**
* the size for the private key in bits (RSA only)
* @default 2048

@@ -42,6 +162,24 @@ */

/**
* additional extensions for the certificate
* The elliptic curve to use (EC only): "P-256", "P-384", or "P-521"
* @default "P-256"
*/
extensions?: any[];
curve?: 'P-256' | 'P-384' | 'P-521'
/**
* Certificate extensions. Supports basicConstraints, keyUsage, extKeyUsage, and subjectAltName.
* If not provided, defaults are used including DNS SAN matching commonName.
* @example
* ```typescript
* extensions: [
* { name: 'basicConstraints', cA: false },
* { name: 'keyUsage', digitalSignature: true, keyEncipherment: true },
* { name: 'subjectAltName', altNames: [
* { type: 2, value: 'localhost' },
* { type: 7, ip: '127.0.0.1' },
* { type: 7, ip: '::1' }
* ]}
* ]
* ```
*/
extensions?: CertificateExtension[];
/**
* The signature algorithm: sha256, sha384, sha512 or sha1

@@ -58,8 +196,10 @@ * @default "sha1"

* generate client cert signed by the original key
* Can be `true` for defaults or an options object
* @default false
*/
clientCertificate?: boolean
clientCertificate?: boolean | ClientCertificateOptions
/**
* client certificate's common name
* @default "John Doe jdoe123"
* @deprecated Use clientCertificate.cn instead
*/

@@ -70,2 +210,3 @@ clientCertificateCN?: string

* @default 2048
* @deprecated Use clientCertificate.keySize instead
*/

@@ -72,0 +213,0 @@ clientCertificateKeySize?: number

+225
-75

@@ -34,4 +34,10 @@ const { X509CertificateGenerator, X509Certificate, X509ChainBuilder, BasicConstraintsExtension, KeyUsagesExtension, KeyUsageFlags, ExtendedKeyUsageExtension, ExtendedKeyUsage, SubjectAlternativeNameExtension, GeneralName } = require("@peculiar/x509");

function getSigningAlgorithm(key) {
const hashAlg = getAlgorithmName(key);
function getSigningAlgorithm(hashKey, keyType) {
const hashAlg = getAlgorithmName(hashKey);
if (keyType === 'ec') {
return {
name: "ECDSA",
hash: hashAlg
};
}
return {

@@ -43,2 +49,114 @@ name: "RSASSA-PKCS1-v1_5",

function getKeyAlgorithm(options) {
const keyType = options.keyType || 'rsa';
const hashAlg = getAlgorithmName(options.algorithm || 'sha1');
if (keyType === 'ec') {
const curve = options.curve || 'P-256';
return {
name: "ECDSA",
namedCurve: curve
};
}
return {
name: "RSASSA-PKCS1-v1_5",
modulusLength: options.keySize || 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: hashAlg
};
}
// Build extensions array from options or use defaults
// Supports the old node-forge extension format for backwards compatibility
function buildExtensions(userExtensions, commonName) {
if (!userExtensions || userExtensions.length === 0) {
// Default extensions
return [
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)
];
}
// Convert user extensions from node-forge format to @peculiar/x509 format
const extensions = [];
for (const ext of userExtensions) {
const critical = ext.critical || false;
switch (ext.name) {
case 'basicConstraints':
extensions.push(new BasicConstraintsExtension(
ext.cA || false,
ext.pathLenConstraint,
critical
));
break;
case 'keyUsage':
let flags = 0;
if (ext.digitalSignature) flags |= KeyUsageFlags.digitalSignature;
if (ext.nonRepudiation || ext.contentCommitment) flags |= KeyUsageFlags.nonRepudiation;
if (ext.keyEncipherment) flags |= KeyUsageFlags.keyEncipherment;
if (ext.dataEncipherment) flags |= KeyUsageFlags.dataEncipherment;
if (ext.keyAgreement) flags |= KeyUsageFlags.keyAgreement;
if (ext.keyCertSign) flags |= KeyUsageFlags.keyCertSign;
if (ext.cRLSign) flags |= KeyUsageFlags.cRLSign;
if (ext.encipherOnly) flags |= KeyUsageFlags.encipherOnly;
if (ext.decipherOnly) flags |= KeyUsageFlags.decipherOnly;
extensions.push(new KeyUsagesExtension(flags, critical));
break;
case 'extKeyUsage':
const usages = [];
if (ext.serverAuth) usages.push(ExtendedKeyUsage.serverAuth);
if (ext.clientAuth) usages.push(ExtendedKeyUsage.clientAuth);
if (ext.codeSigning) usages.push(ExtendedKeyUsage.codeSigning);
if (ext.emailProtection) usages.push(ExtendedKeyUsage.emailProtection);
if (ext.timeStamping) usages.push(ExtendedKeyUsage.timeStamping);
extensions.push(new ExtendedKeyUsageExtension(usages, critical));
break;
case 'subjectAltName':
const altNames = (ext.altNames || []).map(alt => {
// node-forge type values:
// 1 = email (rfc822Name)
// 2 = DNS
// 6 = URI
// 7 = IP
switch (alt.type) {
case 1: // email
return { type: 'email', value: alt.value };
case 2: // DNS
return { type: 'dns', value: alt.value };
case 6: // URI
return { type: 'url', value: alt.value };
case 7: // IP
return { type: 'ip', value: alt.ip || alt.value };
default:
// Try to infer type from properties
if (alt.ip) return { type: 'ip', value: alt.ip };
if (alt.dns) return { type: 'dns', value: alt.dns };
if (alt.email) return { type: 'email', value: alt.email };
if (alt.uri || alt.url) return { type: 'url', value: alt.uri || alt.url };
return { type: 'dns', value: alt.value };
}
});
extensions.push(new SubjectAlternativeNameExtension(altNames, critical));
break;
default:
// Skip unknown extensions with a warning
console.warn(`Unknown extension "${ext.name}" ignored`);
}
}
return extensions;
}
// Convert attributes from node-forge format to X509 name format

@@ -62,46 +180,69 @@ function convertAttributes(attrs) {

// Detect key type from PEM key using Node.js crypto
function detectKeyType(pemKey) {
const keyObject = nodeCrypto.createPrivateKey(pemKey);
return keyObject.asymmetricKeyType; // 'rsa' or 'ec'
}
// Map Node.js curve names to Web Crypto curve names
function normalizeECCurve(curveName) {
const curveMap = {
'prime256v1': 'P-256',
'secp384r1': 'P-384',
'secp521r1': 'P-521',
'P-256': 'P-256',
'P-384': 'P-384',
'P-521': 'P-521'
};
return curveMap[curveName] || curveName;
}
// Get EC curve from key object
function getECCurve(keyObject) {
const details = keyObject.asymmetricKeyDetails;
if (details && details.namedCurve) {
return normalizeECCurve(details.namedCurve);
}
return 'P-256'; // default
}
// 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-----/);
async function importPrivateKey(pemKey, algorithm, keyType) {
// Auto-detect key type if not provided
const keyObject = nodeCrypto.createPrivateKey(pemKey);
const detectedKeyType = keyObject.asymmetricKeyType;
const actualKeyType = keyType || detectedKeyType;
if (pkcs8Match) {
const pemContents = pkcs8Match[1].replace(/\s/g, '');
const binaryDer = Buffer.from(pemContents, 'base64');
return await crypto.subtle.importKey(
'pkcs8',
binaryDer,
{
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: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm),
},
true,
['sign']
);
// Convert to PKCS#8 format
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');
let importAlgorithm;
if (actualKeyType === 'ec') {
const curve = getECCurve(keyObject);
importAlgorithm = {
name: 'ECDSA',
namedCurve: curve
};
} else {
throw new Error('Unsupported private key format. Expected PKCS#8 or PKCS#1 RSA key.');
importAlgorithm = {
name: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm)
};
}
return await crypto.subtle.importKey(
'pkcs8',
binaryDer,
importAlgorithm,
true,
['sign']
);
}
async function importPublicKey(pemKey, algorithm) {
async function importPublicKey(pemKey, algorithm, keyType, curve) {
const pemContents = pemKey

@@ -114,9 +255,19 @@ .replace(/-----BEGIN PUBLIC KEY-----/, '')

let importAlgorithm;
if (keyType === 'ec') {
importAlgorithm = {
name: 'ECDSA',
namedCurve: curve || 'P-256'
};
} else {
importAlgorithm = {
name: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm)
};
}
return await crypto.subtle.importKey(
'spki',
binaryDer,
{
name: 'RSASSA-PKCS1-v1_5',
hash: getAlgorithmName(algorithm),
},
importAlgorithm,
true,

@@ -173,3 +324,4 @@ ['verify']

const subjectName = convertAttributes(attrs);
const signingAlg = getSigningAlgorithm(options.algorithm);
const keyType = options.keyType || 'rsa';
const signingAlg = getSigningAlgorithm(options.algorithm, keyType);

@@ -181,11 +333,3 @@ // Extract common name for SAN extension

// 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)
];
const extensions = buildExtensions(options.extensions, commonName);

@@ -197,3 +341,3 @@ let cert;

const caCert = new X509Certificate(ca.cert);
const caPrivateKey = await importPrivateKey(ca.key, options.algorithm || "sha256");
const caPrivateKey = await importPrivateKey(ca.key, options.algorithm || "sha256", keyType);

@@ -283,10 +427,15 @@ cert = await X509CertificateGenerator.create({

const clientCN = clientOpts.cn || options.clientCertificateCN || "John Doe jdoe123";
// Client cert uses same key type and curve as main cert by default
const clientKeyType = clientOpts.keyType || keyType;
const clientCurve = clientOpts.curve || options.curve || 'P-256';
const clientKeyAlg = getKeyAlgorithm({
keyType: clientKeyType,
keySize: clientKeySize,
algorithm: clientAlgorithm,
curve: clientCurve
});
const clientKeyPair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: clientKeySize,
publicExponent: new Uint8Array([1, 0, 1]),
hash: getAlgorithmName(clientAlgorithm),
},
clientKeyAlg,
true,

@@ -322,4 +471,4 @@ ["sign", "verify"]

// Signing algorithm for client cert (can differ from main cert)
const clientSigningAlg = getSigningAlgorithm(clientAlgorithm);
// Signing algorithm for client cert - uses main key type since signed by root
const clientSigningAlg = getSigningAlgorithm(clientAlgorithm, keyType);

@@ -382,3 +531,5 @@ // Create client cert signed by root key

* @param {object} options
* @param {number} [options.keySize=2048] the size for the private key in bits
* @param {string} [options.keyType="rsa"] Key type: "rsa" or "ec" (elliptic curve)
* @param {number} [options.keySize=2048] the size for the private key in bits (RSA only)
* @param {string} [options.curve="P-256"] The elliptic curve to use: "P-256", "P-384", or "P-521" (EC only)
* @param {object} [options.extensions] additional extensions for the certificate

@@ -389,3 +540,5 @@ * @param {string} [options.algorithm="sha1"] The signature algorithm sha256, sha384, sha512 or sha1

* @param {boolean|object} [options.clientCertificate=false] Generate client cert signed by the original key. Can be `true` for defaults or an options object.
* @param {number} [options.clientCertificate.keySize=2048] Key size for the client certificate in bits
* @param {number} [options.clientCertificate.keySize=2048] Key size for the client certificate in bits (RSA only)
* @param {string} [options.clientCertificate.keyType] Key type for client cert (defaults to main keyType)
* @param {string} [options.clientCertificate.curve] Elliptic curve for client cert (EC only)
* @param {string} [options.clientCertificate.algorithm] Signature algorithm for client cert (defaults to options.algorithm or "sha1")

@@ -407,3 +560,4 @@ * @param {string} [options.clientCertificate.cn="John Doe jdoe123"] Client certificate's common name

const keySize = options.keySize || 2048;
const keyType = options.keyType || 'rsa';
const curve = options.curve || 'P-256';

@@ -415,14 +569,10 @@ let keyPair;

keyPair = {
privateKey: await importPrivateKey(options.keyPair.privateKey, options.algorithm || "sha1"),
publicKey: await importPublicKey(options.keyPair.publicKey, options.algorithm || "sha1")
privateKey: await importPrivateKey(options.keyPair.privateKey, options.algorithm || "sha1", keyType),
publicKey: await importPublicKey(options.keyPair.publicKey, options.algorithm || "sha1", keyType, curve)
};
} else {
// Generate new key pair
// Generate new key pair using appropriate algorithm
const keyAlg = getKeyAlgorithm(options);
keyPair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: keySize,
publicExponent: new Uint8Array([1, 0, 1]),
hash: getAlgorithmName(options.algorithm || "sha1"),
},
keyAlg,
true,

@@ -429,0 +579,0 @@ ["sign", "verify"]

{
"name": "selfsigned",
"version": "5.2.0",
"version": "5.4.0",
"description": "Generate self signed certificates private and public keys",

@@ -8,3 +8,3 @@ "main": "index.js",

"scripts": {
"test": "mocha -t 5000"
"test": "mocha -t 10000"
},

@@ -11,0 +11,0 @@ "repository": {

+159
-2

@@ -42,3 +42,5 @@ # selfsigned

const pems = await selfsigned.generate(null, {
keySize: 2048, // the size for the private key in bits (default: 2048)
keyType: 'rsa', // key type: 'rsa' or 'ec' (default: 'rsa')
keySize: 2048, // the size for the private key in bits (default: 2048, RSA only)
curve: 'P-256', // elliptic curve: 'P-256', 'P-384', or 'P-521' (default: 'P-256', EC only)
notBeforeDate: new Date(), // start of certificate validity (default: now)

@@ -85,2 +87,155 @@ notAfterDate: new Date('2026-01-01'), // end of certificate validity (default: notBeforeDate + 365 days)

### Custom Extensions
You can customize certificate extensions using the `extensions` option. This is useful for adding Subject Alternative Names (SANs) with IPv6 addresses, custom key usage, and more.
```js
const pems = await selfsigned.generate(
[{ name: 'commonName', value: 'localhost' }],
{
extensions: [
{
name: 'basicConstraints',
cA: false
},
{
name: 'keyUsage',
digitalSignature: true,
keyEncipherment: true
},
{
name: 'subjectAltName',
altNames: [
{ type: 2, value: 'localhost' }, // DNS
{ type: 7, ip: '127.0.0.1' }, // IPv4
{ type: 7, ip: '::1' } // IPv6
]
}
]
}
);
```
#### Supported Extensions
**basicConstraints**
```js
{
name: 'basicConstraints',
cA: true, // is this a CA certificate?
pathLenConstraint: 0, // max depth of valid cert chain (optional)
critical: true // mark as critical extension
}
```
**keyUsage**
```js
{
name: 'keyUsage',
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true,
keyAgreement: true,
keyCertSign: true, // for CA certificates
cRLSign: true, // for CA certificates
encipherOnly: true,
decipherOnly: true,
critical: true
}
```
**extKeyUsage** (Extended Key Usage)
```js
{
name: 'extKeyUsage',
serverAuth: true, // TLS server authentication
clientAuth: true, // TLS client authentication
codeSigning: true,
emailProtection: true,
timeStamping: true
}
```
**subjectAltName** (Subject Alternative Name)
```js
{
name: 'subjectAltName',
altNames: [
{ type: 1, value: 'user@example.com' }, // email (rfc822Name)
{ type: 2, value: 'example.com' }, // DNS name
{ type: 2, value: '*.example.com' }, // wildcard DNS
{ type: 6, value: 'http://example.com/webid' }, // URI
{ type: 7, ip: '127.0.0.1' }, // IPv4 address
{ type: 7, ip: '::1' } // IPv6 address
]
}
```
#### Default Extensions
When no `extensions` option is provided (or an empty array), the following defaults are used:
```js
[
{ name: 'basicConstraints', cA: false, critical: true },
{ name: 'keyUsage', digitalSignature: true, keyEncipherment: true, critical: true },
{ name: 'extKeyUsage', serverAuth: true, clientAuth: true },
{ name: 'subjectAltName', altNames: [
{ type: 2, value: commonName },
// For localhost, also includes: { type: 7, ip: '127.0.0.1' }
]}
]
```
### Elliptic Curve (EC) Keys
By default, selfsigned generates RSA keys. You can generate certificates using elliptic curve cryptography instead, which provides equivalent security with smaller key sizes and faster operations.
```js
// Generate EC certificate with P-256 curve (default)
const pems = await selfsigned.generate(null, { keyType: 'ec' });
// Generate EC certificate with P-384 curve
const pems = await selfsigned.generate(null, { keyType: 'ec', curve: 'P-384' });
// Generate EC certificate with P-521 curve and SHA-512
const pems = await selfsigned.generate(null, {
keyType: 'ec',
curve: 'P-521',
algorithm: 'sha512'
});
```
**Supported curves:**
- `P-256` (default) - 128-bit security, fastest
- `P-384` - 192-bit security
- `P-521` - 256-bit security, strongest
EC keys work with all other options including `clientCertificate`, `passphrase`, `ca`, and `keyPair`:
```js
// EC certificate with encrypted private key
const pems = await selfsigned.generate(null, {
keyType: 'ec',
passphrase: 'secret'
});
// EC certificate with client certificate
const pems = await selfsigned.generate(null, {
keyType: 'ec',
clientCertificate: true
});
// Reuse existing EC key pair
const pems = await selfsigned.generate(null, {
keyType: 'ec',
curve: 'P-256',
keyPair: {
publicKey: existingPublicKey,
privateKey: existingPrivateKey
}
});
```
### Using Your Own Keys

@@ -239,3 +394,5 @@

cn: 'jdoe', // common name (default: 'John Doe jdoe123')
keySize: 4096, // key size in bits (default: 2048)
keyType: 'rsa', // key type: 'rsa' or 'ec' (default: inherits from parent)
keySize: 4096, // key size in bits (default: 2048, RSA only)
curve: 'P-256', // elliptic curve (default: 'P-256', EC only)
algorithm: 'sha256', // signature algorithm (default: inherits from parent or 'sha1')

@@ -242,0 +399,0 @@ notBeforeDate: new Date(), // validity start (default: now)

@@ -373,2 +373,195 @@ var { assert } = require('chai');

describe('extensions', function () {
it('should support custom subjectAltName with IPv6 (issue #79)', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'localhost' }],
{
algorithm: 'sha256',
extensions: [
{
name: 'basicConstraints',
cA: false
},
{
name: 'keyUsage',
digitalSignature: true,
keyEncipherment: true
},
{
name: 'subjectAltName',
altNames: [
{ type: 2, value: 'localhost' }, // DNS
{ type: 7, ip: '127.0.0.1' }, // IPv4
{ type: 7, ip: '::1' } // IPv6
]
}
]
}
);
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.subjectAltName, 'should have subjectAltName');
assert.include(cert.subjectAltName, 'localhost', 'should include DNS name');
assert.include(cert.subjectAltName, '127.0.0.1', 'should include IPv4');
// IPv6 ::1 may be expanded to full form 0:0:0:0:0:0:0:1
const hasIPv6 = cert.subjectAltName.includes('::1') || cert.subjectAltName.includes('0:0:0:0:0:0:0:1');
assert.ok(hasIPv6, 'should include IPv6');
});
it('should support basicConstraints with cA=true', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'Test CA' }],
{
extensions: [
{
name: 'basicConstraints',
cA: true,
critical: true
},
{
name: 'keyUsage',
keyCertSign: true,
cRLSign: true,
critical: true
}
]
}
);
const cert = new crypto.X509Certificate(pems.cert);
assert.ok(cert.ca, 'certificate should be a CA');
});
it('should support keyUsage extension', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'test.example.com' }],
{
extensions: [
{
name: 'basicConstraints',
cA: false
},
{
name: 'keyUsage',
digitalSignature: true,
keyEncipherment: true,
dataEncipherment: true
}
]
}
);
const cert = new crypto.X509Certificate(pems.cert);
// Node.js X509Certificate doesn't expose keyUsage directly,
// but we can verify the cert is valid and can be used
assert.ok(cert.publicKey, 'should generate valid cert with keyUsage');
// Verify by using openssl to check extensions
const fs = require('fs');
fs.writeFileSync('/tmp/test-keyusage.crt', pems.cert);
const { execSync } = require('child_process');
const output = execSync('openssl x509 -in /tmp/test-keyusage.crt -text -noout').toString();
assert.include(output, 'Digital Signature', 'should have digitalSignature');
assert.include(output, 'Key Encipherment', 'should have keyEncipherment');
});
it('should support extKeyUsage extension', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'test.example.com' }],
{
extensions: [
{
name: 'basicConstraints',
cA: false
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true
}
]
}
);
const cert = new crypto.X509Certificate(pems.cert);
// Node.js crypto doesn't expose extended key usage directly, but cert should be valid
assert.ok(cert.publicKey, 'should generate valid cert with extKeyUsage');
});
it('should support subjectAltName with DNS names', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'example.com' }],
{
extensions: [
{
name: 'basicConstraints',
cA: false
},
{
name: 'subjectAltName',
altNames: [
{ type: 2, value: 'example.com' },
{ type: 2, value: 'www.example.com' },
{ type: 2, value: '*.example.com' }
]
}
]
}
);
const cert = new crypto.X509Certificate(pems.cert);
assert.include(cert.subjectAltName, 'example.com', 'should include example.com');
assert.include(cert.subjectAltName, 'www.example.com', 'should include www.example.com');
assert.include(cert.subjectAltName, '*.example.com', 'should include wildcard');
});
it('should support subjectAltName with email and URI', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'test.example.com' }],
{
extensions: [
{
name: 'basicConstraints',
cA: false
},
{
name: 'subjectAltName',
altNames: [
{ type: 2, value: 'test.example.com' },
{ type: 1, value: 'admin@example.com' }, // email
{ type: 6, value: 'http://example.com/webid#me' } // URI
]
}
]
}
);
const cert = new crypto.X509Certificate(pems.cert);
assert.include(cert.subjectAltName, 'test.example.com', 'should include DNS');
assert.include(cert.subjectAltName, 'admin@example.com', 'should include email');
assert.include(cert.subjectAltName, 'http://example.com/webid#me', 'should include URI');
});
it('should use default extensions when extensions option is empty array', async function () {
var pems = await generate(
[{ name: 'commonName', value: 'localhost' }],
{
extensions: []
}
);
const cert = new crypto.X509Certificate(pems.cert);
// Default behavior includes localhost and 127.0.0.1
assert.include(cert.subjectAltName, 'localhost', 'should use default SAN');
assert.include(cert.subjectAltName, '127.0.0.1', 'should include default IP for localhost');
});
it('should use default extensions when extensions option is not provided', async function () {
var pems = await generate([{ name: 'commonName', value: 'myhost.local' }]);
const cert = new crypto.X509Certificate(pems.cert);
assert.include(cert.subjectAltName, 'myhost.local', 'should use commonName in default SAN');
});
});
it('should support passphrase for private key encryption', async function () {

@@ -375,0 +568,0 @@ const passphrase = 'my-secret-passphrase';