self-sign 5.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of self-sign might be problematic. Click here for more details.
- package/.claude/settings.local.json +14 -0
- package/.github/workflows/pr-tests.yml +27 -0
- package/.jshintrc +39 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +97 -0
- package/LICENSE +22 -0
- package/README.md +474 -0
- package/examples/https-server-mkcert.js +66 -0
- package/examples/https-server.js +32 -0
- package/index.d.ts +276 -0
- package/index.js +583 -0
- package/package.json +36 -0
- package/pkcs7.js +70 -0
- package/test/ca-signing.js +242 -0
- package/test/ec-keys.js +142 -0
- package/test/tests.js +605 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
var { assert } = require('chai');
|
|
2
|
+
var crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
describe('CA signing', function () {
|
|
5
|
+
var generate = require('../index').generate;
|
|
6
|
+
|
|
7
|
+
it('should generate certificate signed by provided CA', async function () {
|
|
8
|
+
// First generate a self-signed CA certificate
|
|
9
|
+
const ca = await generate([
|
|
10
|
+
{ name: 'commonName', value: 'Test CA' },
|
|
11
|
+
{ name: 'organizationName', value: 'Test Organization' }
|
|
12
|
+
], {
|
|
13
|
+
algorithm: 'sha256'
|
|
14
|
+
},null);
|
|
15
|
+
|
|
16
|
+
// Generate a certificate signed by the CA
|
|
17
|
+
const pems = await generate([
|
|
18
|
+
{ name: 'commonName', value: 'localhost' }
|
|
19
|
+
], {
|
|
20
|
+
algorithm: 'sha256',
|
|
21
|
+
ca: {
|
|
22
|
+
key: ca.private,
|
|
23
|
+
cert: ca.cert
|
|
24
|
+
}
|
|
25
|
+
},null);
|
|
26
|
+
|
|
27
|
+
assert.ok(!!pems.private, 'has a private key');
|
|
28
|
+
assert.ok(!!pems.public, 'has a public key');
|
|
29
|
+
assert.ok(!!pems.cert, 'has a certificate');
|
|
30
|
+
assert.ok(!!pems.fingerprint, 'has fingerprint');
|
|
31
|
+
|
|
32
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
33
|
+
const caCert = new crypto.X509Certificate(ca.cert);
|
|
34
|
+
|
|
35
|
+
// Verify issuer is the CA, not self-signed
|
|
36
|
+
assert.include(cert.issuer, 'CN=Test CA', 'issuer should be the CA');
|
|
37
|
+
assert.include(cert.subject, 'CN=localhost', 'subject should be localhost');
|
|
38
|
+
assert.notEqual(cert.issuer, cert.subject, 'should not be self-signed');
|
|
39
|
+
|
|
40
|
+
// Verify the certificate is signed by the CA
|
|
41
|
+
assert.isTrue(cert.verify(caCert.publicKey), 'certificate should be verified by CA public key');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should include Subject Alternative Name extension', async function () {
|
|
45
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
46
|
+
algorithm: 'sha256'
|
|
47
|
+
},null);
|
|
48
|
+
|
|
49
|
+
const pems = await generate([
|
|
50
|
+
{ name: 'commonName', value: 'example.com' }
|
|
51
|
+
], {
|
|
52
|
+
algorithm: 'sha256',
|
|
53
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
54
|
+
},null);
|
|
55
|
+
|
|
56
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
57
|
+
assert.include(cert.subjectAltName, 'DNS:example.com', 'should have DNS SAN matching CN');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should include IP SAN for localhost', async function () {
|
|
61
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
62
|
+
algorithm: 'sha256'
|
|
63
|
+
},null);
|
|
64
|
+
|
|
65
|
+
const pems = await generate([
|
|
66
|
+
{ name: 'commonName', value: 'localhost' }
|
|
67
|
+
], {
|
|
68
|
+
algorithm: 'sha256',
|
|
69
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
70
|
+
},null);
|
|
71
|
+
|
|
72
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
73
|
+
assert.include(cert.subjectAltName, 'DNS:localhost', 'should have DNS SAN');
|
|
74
|
+
assert.include(cert.subjectAltName, 'IP Address:127.0.0.1', 'should have IP SAN for localhost');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should support different hash algorithms with CA signing', async function () {
|
|
78
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
79
|
+
algorithm: 'sha256'
|
|
80
|
+
},null);
|
|
81
|
+
|
|
82
|
+
// Test sha384
|
|
83
|
+
const pems384 = await generate([{ name: 'commonName', value: 'test384.local' }], {
|
|
84
|
+
algorithm: 'sha384',
|
|
85
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
86
|
+
},null);
|
|
87
|
+
|
|
88
|
+
const cert384 = new crypto.X509Certificate(pems384.cert);
|
|
89
|
+
assert.ok(cert384.publicKey, 'should generate sha384 CA-signed cert');
|
|
90
|
+
|
|
91
|
+
// Test sha512
|
|
92
|
+
const pems512 = await generate([{ name: 'commonName', value: 'test512.local' }], {
|
|
93
|
+
algorithm: 'sha512',
|
|
94
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
95
|
+
},null);
|
|
96
|
+
|
|
97
|
+
const cert512 = new crypto.X509Certificate(pems512.cert);
|
|
98
|
+
assert.ok(cert512.publicKey, 'should generate sha512 CA-signed cert');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should respect notAfterDate option with CA signing', async function () {
|
|
102
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
103
|
+
algorithm: 'sha256'
|
|
104
|
+
},null);
|
|
105
|
+
|
|
106
|
+
const notBefore = new Date('2025-01-01T00:00:00Z');
|
|
107
|
+
const notAfter = new Date('2025-01-31T00:00:00Z'); // 30 days validity
|
|
108
|
+
const pems = await generate([{ name: 'commonName', value: 'short-lived.local' }], {
|
|
109
|
+
algorithm: 'sha256',
|
|
110
|
+
notBeforeDate: notBefore,
|
|
111
|
+
notAfterDate: notAfter,
|
|
112
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
116
|
+
const validFrom = new Date(cert.validFrom);
|
|
117
|
+
const validTo = new Date(cert.validTo);
|
|
118
|
+
|
|
119
|
+
assert.approximately(validFrom.getTime(), notBefore.getTime(), 5000, 'should use custom notBeforeDate');
|
|
120
|
+
assert.approximately(validTo.getTime(), notAfter.getTime(), 5000, 'should use custom notAfterDate');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should generate unique certificates with same CA', async function () {
|
|
124
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
125
|
+
algorithm: 'sha256'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const pems1 = await generate([{ name: 'commonName', value: 'test1.local' }], {
|
|
129
|
+
algorithm: 'sha256',
|
|
130
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const pems2 = await generate([{ name: 'commonName', value: 'test2.local' }], {
|
|
134
|
+
algorithm: 'sha256',
|
|
135
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const cert1 = new crypto.X509Certificate(pems1.cert);
|
|
139
|
+
const cert2 = new crypto.X509Certificate(pems2.cert);
|
|
140
|
+
|
|
141
|
+
assert.notEqual(cert1.serialNumber, cert2.serialNumber, 'serial numbers should be unique');
|
|
142
|
+
assert.notEqual(pems1.private, pems2.private, 'private keys should be different');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should work with custom keySize and CA signing', async function () {
|
|
146
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
147
|
+
algorithm: 'sha256',
|
|
148
|
+
keySize: 4096
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const pems = await generate([{ name: 'commonName', value: 'bigkey.local' }], {
|
|
152
|
+
algorithm: 'sha256',
|
|
153
|
+
keySize: 4096,
|
|
154
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const privateKey = crypto.createPrivateKey(pems.private);
|
|
158
|
+
assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 4096, 'should use custom key size');
|
|
159
|
+
|
|
160
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
161
|
+
const caCert = new crypto.X509Certificate(ca.cert);
|
|
162
|
+
assert.isTrue(cert.verify(caCert.publicKey), 'certificate should verify with CA');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should support existing keyPair with CA signing', async function () {
|
|
166
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
167
|
+
algorithm: 'sha256'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Generate a key pair first
|
|
171
|
+
const keyPair = await generate([{ name: 'commonName', value: 'keypair.local' }], {
|
|
172
|
+
algorithm: 'sha256'
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Use existing key pair with CA signing
|
|
176
|
+
const pems = await generate([{ name: 'commonName', value: 'reused.local' }], {
|
|
177
|
+
algorithm: 'sha256',
|
|
178
|
+
keyPair: {
|
|
179
|
+
privateKey: keyPair.private,
|
|
180
|
+
publicKey: keyPair.public
|
|
181
|
+
},
|
|
182
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
assert.strictEqual(pems.private, keyPair.private, 'should use provided private key');
|
|
186
|
+
assert.strictEqual(pems.public, keyPair.public, 'should use provided public key');
|
|
187
|
+
|
|
188
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
189
|
+
const caCert = new crypto.X509Certificate(ca.cert);
|
|
190
|
+
assert.isTrue(cert.verify(caCert.publicKey), 'certificate should verify with CA');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should include proper extended key usage extensions', async function () {
|
|
194
|
+
const ca = await generate([{ name: 'commonName', value: 'Test CA' }], {
|
|
195
|
+
algorithm: 'sha256'
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const pems = await generate([{ name: 'commonName', value: 'server.local' }], {
|
|
199
|
+
algorithm: 'sha256',
|
|
200
|
+
ca: { key: ca.private, cert: ca.cert }
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
204
|
+
|
|
205
|
+
// Check extended key usage (OIDs)
|
|
206
|
+
// 1.3.6.1.5.5.7.3.1 = serverAuth
|
|
207
|
+
// 1.3.6.1.5.5.7.3.2 = clientAuth
|
|
208
|
+
assert.include(cert.keyUsage, '1.3.6.1.5.5.7.3.1', 'should have serverAuth extended key usage');
|
|
209
|
+
assert.include(cert.keyUsage, '1.3.6.1.5.5.7.3.2', 'should have clientAuth extended key usage');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should work with PKCS#1 RSA key format', async function () {
|
|
213
|
+
// Generate a CA with PKCS#1 format key (like mkcert uses)
|
|
214
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
|
215
|
+
modulusLength: 2048,
|
|
216
|
+
privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
|
217
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' }
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Create a self-signed CA cert using the PKCS#1 key
|
|
221
|
+
const ca = await generate([{ name: 'commonName', value: 'PKCS1 CA' }], {
|
|
222
|
+
algorithm: 'sha256'
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Now test that we can use a PKCS#1 formatted key as CA
|
|
226
|
+
// Convert our generated key to PKCS#1 for testing
|
|
227
|
+
const caKeyObject = crypto.createPrivateKey(ca.private);
|
|
228
|
+
const pkcs1Key = caKeyObject.export({ type: 'pkcs1', format: 'pem' });
|
|
229
|
+
|
|
230
|
+
const pems = await generate([{ name: 'commonName', value: 'pkcs1-test.local' }], {
|
|
231
|
+
algorithm: 'sha256',
|
|
232
|
+
ca: {
|
|
233
|
+
key: pkcs1Key,
|
|
234
|
+
cert: ca.cert
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
239
|
+
const caCert = new crypto.X509Certificate(ca.cert);
|
|
240
|
+
assert.isTrue(cert.verify(caCert.publicKey), 'should work with PKCS#1 key format');
|
|
241
|
+
});
|
|
242
|
+
});
|
package/test/ec-keys.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
var { assert } = require('chai');
|
|
2
|
+
var crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
describe('EC keys', function () {
|
|
5
|
+
var generate = require('../index').generate;
|
|
6
|
+
|
|
7
|
+
it('should generate EC certificate with P-256 curve (default)', async function () {
|
|
8
|
+
var pems = await generate(null, { keyType: 'ec' },null);
|
|
9
|
+
|
|
10
|
+
assert.ok(!!pems.private, 'has a private key');
|
|
11
|
+
assert.ok(!!pems.public, 'has a public key');
|
|
12
|
+
assert.ok(!!pems.cert, 'has a certificate');
|
|
13
|
+
assert.ok(!!pems.fingerprint, 'has fingerprint');
|
|
14
|
+
|
|
15
|
+
const privateKey = crypto.createPrivateKey(pems.private);
|
|
16
|
+
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'should be EC key');
|
|
17
|
+
assert.strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'prime256v1', 'should use P-256 curve');
|
|
18
|
+
|
|
19
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
20
|
+
assert.ok(cert.subject, 'cert has a subject');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should generate EC certificate with P-384 curve', async function () {
|
|
24
|
+
var pems = await generate(null, { keyType: 'ec', curve: 'P-384' },null);
|
|
25
|
+
|
|
26
|
+
const privateKey = crypto.createPrivateKey(pems.private);
|
|
27
|
+
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'should be EC key');
|
|
28
|
+
assert.strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'secp384r1', 'should use P-384 curve');
|
|
29
|
+
|
|
30
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
31
|
+
assert.ok(cert.publicKey, 'can generate P-384 EC certs');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should generate EC certificate with P-521 curve', async function () {
|
|
35
|
+
var pems = await generate(null, { keyType: 'ec', curve: 'P-521' },null);
|
|
36
|
+
|
|
37
|
+
const privateKey = crypto.createPrivateKey(pems.private);
|
|
38
|
+
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'should be EC key');
|
|
39
|
+
assert.strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'secp521r1', 'should use P-521 curve');
|
|
40
|
+
|
|
41
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
42
|
+
assert.ok(cert.publicKey, 'can generate P-521 EC certs');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should generate valid EC key pair that work together', async function () {
|
|
46
|
+
var pems = await generate(null, { keyType: 'ec', curve: 'P-256' },null);
|
|
47
|
+
|
|
48
|
+
const testData = 'Hello, World!';
|
|
49
|
+
|
|
50
|
+
const privateKey = crypto.createPrivateKey(pems.private);
|
|
51
|
+
const publicKey = crypto.createPublicKey(pems.public);
|
|
52
|
+
|
|
53
|
+
// Sign with private key
|
|
54
|
+
const sign = crypto.createSign('SHA256');
|
|
55
|
+
sign.update(testData);
|
|
56
|
+
sign.end();
|
|
57
|
+
const signature = sign.sign(privateKey);
|
|
58
|
+
|
|
59
|
+
// Verify with public key
|
|
60
|
+
const verify = crypto.createVerify('SHA256');
|
|
61
|
+
verify.update(testData);
|
|
62
|
+
verify.end();
|
|
63
|
+
const isValid = verify.verify(publicKey, signature);
|
|
64
|
+
|
|
65
|
+
assert.isTrue(isValid, 'EC public key should verify signature from EC private key');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should support EC with sha256 algorithm', async function () {
|
|
69
|
+
var pems = await generate(null, { keyType: 'ec', algorithm: 'sha256' },null);
|
|
70
|
+
|
|
71
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
72
|
+
assert.ok(cert.publicKey, 'can generate EC cert with sha256');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should support EC with sha384 algorithm', async function () {
|
|
76
|
+
var pems = await generate(null, { keyType: 'ec', curve: 'P-384', algorithm: 'sha384' },null);
|
|
77
|
+
|
|
78
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
79
|
+
assert.ok(cert.publicKey, 'can generate EC cert with sha384');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should support EC with sha512 algorithm', async function () {
|
|
83
|
+
var pems = await generate(null, { keyType: 'ec', curve: 'P-521', algorithm: 'sha512' },null);
|
|
84
|
+
|
|
85
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
86
|
+
assert.ok(cert.publicKey, 'can generate EC cert with sha512');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should generate EC client certificate', async function () {
|
|
90
|
+
var pems = await generate(null, { keyType: 'ec', clientCertificate: true },null);
|
|
91
|
+
|
|
92
|
+
assert.ok(!!pems.clientcert, 'should include a client cert');
|
|
93
|
+
assert.ok(!!pems.clientprivate, 'should include a client private key');
|
|
94
|
+
assert.ok(!!pems.clientpublic, 'should include a client public key');
|
|
95
|
+
|
|
96
|
+
const clientPrivateKey = crypto.createPrivateKey(pems.clientprivate);
|
|
97
|
+
assert.strictEqual(clientPrivateKey.asymmetricKeyType, 'ec', 'client key should be EC');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should support passphrase with EC keys', async function () {
|
|
101
|
+
const passphrase = 'ec-secret-passphrase';
|
|
102
|
+
var pems = await generate(null, { keyType: 'ec', passphrase: passphrase },null);
|
|
103
|
+
|
|
104
|
+
assert.include(pems.private, 'ENCRYPTED', 'EC private key should be encrypted');
|
|
105
|
+
|
|
106
|
+
const privateKey = crypto.createPrivateKey({
|
|
107
|
+
key: pems.private,
|
|
108
|
+
passphrase: passphrase
|
|
109
|
+
});
|
|
110
|
+
assert.strictEqual(privateKey.asymmetricKeyType, 'ec', 'decrypted key should be EC');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should support using existing EC keyPair', async function () {
|
|
114
|
+
const firstPems = await generate(null, { keyType: 'ec', curve: 'P-256' },null);
|
|
115
|
+
|
|
116
|
+
const secondPems = await generate(null, {
|
|
117
|
+
keyType: 'ec',
|
|
118
|
+
curve: 'P-256',
|
|
119
|
+
keyPair: {
|
|
120
|
+
privateKey: firstPems.private,
|
|
121
|
+
publicKey: firstPems.public
|
|
122
|
+
}
|
|
123
|
+
},null);
|
|
124
|
+
|
|
125
|
+
assert.strictEqual(firstPems.private, secondPems.private, 'should use provided EC private key');
|
|
126
|
+
assert.strictEqual(firstPems.public, secondPems.public, 'should use provided EC public key');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should support custom attributes with EC', async function () {
|
|
130
|
+
const attrs = [
|
|
131
|
+
{ name: 'commonName', value: 'ec-test.example.com' },
|
|
132
|
+
{ name: 'countryName', value: 'US' },
|
|
133
|
+
{ name: 'organizationName', value: 'EC Test Corp' }
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
var pems = await generate(attrs, { keyType: 'ec' },null);
|
|
137
|
+
const cert = new crypto.X509Certificate(pems.cert);
|
|
138
|
+
|
|
139
|
+
assert.include(cert.subject, 'CN=ec-test.example.com', 'should include custom CN');
|
|
140
|
+
assert.include(cert.subject, 'O=EC Test Corp', 'should include custom organization');
|
|
141
|
+
});
|
|
142
|
+
});
|