Skip to content

Commit a521b2d

Browse files
committed
feat: add function to cleanup the trust store
resolves #20
1 parent 1e3793a commit a521b2d

18 files changed

+775
-109
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ docs/.vitepress/dist
1515
docs/.vitepress/cache
1616
storage
1717
rp
18+
.env

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Given the npm package is installed:
4949

5050
```ts
5151
import type { AddCertOptions, CAOptions, CertificateOptions, TlsConfig, TlsOptions } from '@stacksjs/tlsx'
52-
import { addCertToSystemTrustStoreAndSaveCerts, config, forge, generateCert, pki, storeCertificate, tls } from '@stacksjs/tlsx'
52+
import { addCertToSystemTrustStoreAndSaveCert, cleanupTrustStore, config, forge, generateCertificate, pki, removeCertFromSystemTrustStore, storeCertificate, tls } from '@stacksjs/tlsx'
5353

5454
// Generate a certificate for a single domain
5555
const cert = await generateCertificate({
@@ -75,6 +75,18 @@ const combinedCert = await generateCertificate({
7575

7676
// Store and trust the certificate
7777
await addCertToSystemTrustStoreAndSaveCert(cert, rootCA.certificate)
78+
79+
// Remove a specific certificate
80+
await removeCertFromSystemTrustStore('example.com')
81+
82+
// Remove a certificate with a specific name
83+
await removeCertFromSystemTrustStore('example.com', {}, 'My Custom Certificate Name')
84+
85+
// Clean up all TLSX certificates from the system trust store
86+
await cleanupTrustStore()
87+
88+
// Clean up certificates matching a specific pattern
89+
await cleanupTrustStore({}, 'My Custom Pattern')
7890
```
7991

8092
### CLI
@@ -92,6 +104,27 @@ tlsx secure example.com -d "api.example.com,*.example.com"
92104
# Generate certificate with custom validity and organization
93105
tlsx secure example.com --validity-days 365 --organization-name "My Company"
94106

107+
# Revoke a certificate for a domain
108+
tlsx revoke example.com
109+
110+
# Revoke a certificate with a specific name
111+
tlsx revoke example.com --cert-name "My Custom Certificate"
112+
113+
# Clean up all TLSX certificates from the system trust store
114+
tlsx cleanup
115+
116+
# Clean up certificates matching a specific pattern
117+
tlsx cleanup --pattern "My Custom Pattern"
118+
119+
# List all certificates
120+
tlsx list
121+
122+
# Verify a certificate
123+
tlsx verify path/to/cert.crt
124+
125+
# Show system configuration and paths
126+
tlsx info
127+
95128
# Show all available options
96129
tlsx secure --help
97130

@@ -159,7 +192,7 @@ For casual chit-chat with others using this package:
159192

160193
## Postcardware
161194

162-
Software that is free, but hopes for a postcard. We love receiving postcards from around the world showing where `tlsx` is being used! We showcase them on our website too.
195+
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where `tlsx` is being used! We showcase them on our website too.
163196

164197
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
165198

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "tlsx",
33
"type": "module",
4-
"private": true,
54
"version": "0.11.0",
5+
"private": true,
66
"description": "A TLS/HTTPS library with automation.",
77
"author": "Chris Breuer <chris@stacksjs.org>",
88
"license": "MIT",

packages/tlsx/bin/cli.ts

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import os from 'node:os'
2+
import process from 'node:process'
13
import { CAC } from 'cac'
24
import { consola as log } from 'consola'
3-
import os from 'node:os'
45
import { version } from '../package.json'
5-
import { addCertToSystemTrustStoreAndSaveCert, createRootCA, generateCertificate, removeCertFromSystemTrustStore } from '../src/certificate'
6+
import { addCertToSystemTrustStoreAndSaveCert, cleanupTrustStore, createRootCA, generateCertificate, removeCertFromSystemTrustStore } from '../src/certificate'
67
import { validateCertificate } from '../src/certificate/validation'
78
import { config } from '../src/config'
89
import { listCertsInDirectory, normalizeCertPaths } from '../src/utils'
@@ -112,11 +113,13 @@ cli
112113
.option('-ca, --ca-path <ca>', 'CA file path', { default: config.caCertPath })
113114
.option('-c, --cert-path <cert>', 'Certificate file path', { default: config.certPath })
114115
.option('-k, --key-path <key>', 'Key file path', { default: config.keyPath })
116+
.option('--cert-name <name>', 'Specific certificate name to revoke')
115117
.option('--verbose', 'Enable verbose logging', { default: config.verbose })
116118
.usage('tlsx revoke [domain] [options]')
117119
.example('tlsx revoke example.com')
118120
.example('tlsx revoke example.com --ca-path /path/to/ca.crt')
119-
.action(async (domain?: string, options?: Omit<CliOptions, 'domains'>) => {
121+
.example('tlsx revoke example.com --cert-name "My Custom Cert Name"')
122+
.action(async (domain?: string, options?: Omit<CliOptions, 'domains'> & { 'cert-name'?: string }) => {
120123
const cliOptions = options || {}
121124

122125
// Validate that domain is provided
@@ -125,9 +128,10 @@ cli
125128
}
126129

127130
const domainToRevoke = domain || config.domain
131+
const certName = options?.['cert-name']
128132

129-
log.info(`Revoking certificate for domain: ${domainToRevoke}`)
130-
log.debug('Options:', { ...cliOptions, domain: domainToRevoke })
133+
log.info(`Revoking certificate for domain: ${domainToRevoke}${certName ? ` with name: ${certName}` : ''}`)
134+
log.debug('Options:', { ...cliOptions, domain: domainToRevoke, certName })
131135

132136
try {
133137
// Call the implemented certificate revocation function
@@ -136,10 +140,11 @@ cli
136140
certPath: cliOptions.certPath || config.certPath,
137141
keyPath: cliOptions.keyPath || config.keyPath,
138142
verbose: cliOptions.verbose,
139-
})
143+
}, certName)
140144

141-
log.success(`Certificate for ${domainToRevoke} has been revoked`)
142-
} catch (error) {
145+
log.success(`Certificate for ${domainToRevoke}${certName ? ` with name: ${certName}` : ''} has been revoked`)
146+
}
147+
catch (error) {
143148
log.error(`Failed to revoke certificate for ${domainToRevoke}: ${error}`)
144149
process.exit(1)
145150
}
@@ -169,7 +174,8 @@ cli
169174
certificates.forEach((certPath, index) => {
170175
log.info(`${index + 1}. ${certPath}`)
171176
})
172-
} catch (error) {
177+
}
178+
catch (error) {
173179
log.error(`Failed to list certificates: ${error}`)
174180
process.exit(1)
175181
}
@@ -182,7 +188,7 @@ cli
182188
.usage('tlsx verify [cert-path] [options]')
183189
.example('tlsx verify /path/to/cert.crt')
184190
.example('tlsx verify /path/to/cert.crt --ca-path /path/to/ca.crt')
185-
.action(async (certPath?: string, options?: { 'ca-path'?: string, verbose?: boolean }) => {
191+
.action(async (certPath?: string, options?: { 'ca-path'?: string, 'verbose'?: boolean }) => {
186192
// If no certificate path is provided, use the default
187193
const certificatePath = certPath || config.certPath
188194
const caCertPath = options?.['ca-path'] || config.caCertPath
@@ -282,6 +288,52 @@ cli
282288
}
283289
})
284290

291+
cli
292+
.command('cleanup', 'Clean up all TLSX certificates from the system trust store')
293+
.option('--force', 'Skip confirmation prompt', { default: false })
294+
.option('--verbose', 'Enable verbose logging', { default: config.verbose })
295+
.option('--pattern <pattern>', 'Certificate name pattern to match for cleanup')
296+
.usage('tlsx cleanup [options]')
297+
.example('tlsx cleanup')
298+
.example('tlsx cleanup --force')
299+
.example('tlsx cleanup --pattern "My Custom Cert"')
300+
.action(async (options?: { force?: boolean, verbose?: boolean, pattern?: string }) => {
301+
const force = options?.force || false
302+
const verbose = options?.verbose || config.verbose
303+
const pattern = options?.pattern
304+
305+
if (!force) {
306+
log.warn(`This will remove ${pattern ? `certificates matching "${pattern}"` : 'all TLSX certificates'} from your system trust store.`)
307+
log.warn('This action cannot be undone.')
308+
309+
// Simple confirmation prompt
310+
process.stdout.write('Are you sure you want to continue? (y/N): ')
311+
312+
const response = await new Promise<string>((resolve) => {
313+
process.stdin.once('data', (data) => {
314+
resolve(data.toString().trim().toLowerCase())
315+
})
316+
})
317+
318+
if (response !== 'y' && response !== 'yes') {
319+
log.info('Operation cancelled.')
320+
return
321+
}
322+
}
323+
324+
log.info(`Cleaning up ${pattern ? `certificates matching "${pattern}"` : 'all TLSX certificates'} from system trust store...`)
325+
326+
try {
327+
await cleanupTrustStore({ verbose }, pattern)
328+
329+
log.success(`${pattern ? `Certificates matching "${pattern}"` : 'All TLSX certificates'} have been removed from the system trust store`)
330+
}
331+
catch (error) {
332+
log.error(`Failed to clean up certificates: ${error}`)
333+
process.exit(1)
334+
}
335+
})
336+
285337
cli.version(version)
286338
cli.help()
287339
cli.parse()

packages/tlsx/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,4 @@
7474
"workspaces": [
7575
"packages/*"
7676
]
77-
}
77+
}

packages/tlsx/src/certificate/generate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import forge, { pki } from 'node-forge'
21
import type { CAOptions, Certificate, CertificateOptions } from '../types'
2+
import forge, { pki } from 'node-forge'
33
import { config } from '../config'
44
import { CERT_CONSTANTS } from '../constants'
55
import { debugLog, getPrimaryDomain } from '../utils'
@@ -127,4 +127,4 @@ export async function generateCertificate(options: CertificateOptions): Promise<
127127
notBefore,
128128
notAfter,
129129
}
130-
}
130+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Re-export all certificate modules
2+
// Re-export forge for compatibility
3+
import forge, { pki, tls } from 'node-forge'
4+
25
export * from './generate'
36
export * from './store'
47
export * from './trust'
58
export * from './utils'
69
export * from './validation'
7-
8-
// Re-export forge for compatibility
9-
import forge, { pki, tls } from 'node-forge'
1010
export { forge, pki, tls }

packages/tlsx/src/certificate/store.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
import type { Cert, CertPath, TlsOption } from '../types'
12
import fs from 'node:fs'
23
import path from 'node:path'
3-
import type { Cert, CertPath, TlsOption } from '../types'
4-
import { config } from '../config'
54
import { LOG_CATEGORIES } from '../constants'
65
import { debugLog, normalizeCertPaths } from '../utils'
76

@@ -73,4 +72,4 @@ export function storeCACertificate(caCert: string, options?: TlsOption): CertPat
7372

7473
debugLog(LOG_CATEGORIES.STORAGE, 'CA certificate stored successfully', options?.verbose)
7574
return caCertPath
76-
}
75+
}

0 commit comments

Comments
 (0)