{"_id":"@delali/narsil-certutil","_rev":"2-aa7ce0d32a55c1aa601064aeadbc5de8","name":"@delali/narsil-certutil","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@delali/narsil-certutil","version":"0.1.0","author":{"url":"https://sondelali.com","name":"Delali"},"license":"Apache-2.0","_id":"@delali/narsil-certutil@0.1.0","maintainers":[{"name":"assetcorp","email":"delalify@gmail.com"}],"homepage":"https://github.com/assetcorp/narsil","bugs":{"url":"https://github.com/assetcorp/narsil/issues"},"bin":{"narsil-certutil":"dist/cli.mjs"},"dist":{"shasum":"f32aefd7860c3a8abcbcd706975ad5283ba1abfa","tarball":"https://registry.npmjs.org/@delali/narsil-certutil/-/narsil-certutil-0.1.0.tgz","fileCount":6,"integrity":"sha512-6IubtoePNG6m/wUqnkiuencFUFTgbGZKVqs1ZTCBY2+C1G/8PspL358mdefn8whkUBLFZQs9lq9X0ngWN0C8Pg==","signatures":[{"sig":"MEQCIESRhI68+Fs1KWa8q0STzpIPjkrfwBSiUpYCpRM3f96UAiB9EqrMNtVfpYuun0yXV7Dlg1UaO1hNgrcZwawWFYMiwQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":83577},"type":"module","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs"}},"gitHead":"ff57ab5240015d621b84f6edb280ca7cac559439","scripts":{"lint":"biome check src/","test":"vitest run","build":"tsup","typecheck":"pnpm run typecheck:src && pnpm run typecheck:tests","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck:src":"tsc --noEmit","typecheck:tests":"tsc --noEmit -p tsconfig.test.json"},"_npmUser":{"name":"assetcorp","email":"delalify@gmail.com"},"repository":{"url":"git+https://github.com/assetcorp/narsil.git","type":"git","directory":"packages/certutil"},"_npmVersion":"11.13.0","description":"CLI tool for generating and managing TLS certificates for Narsil clusters.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"yaml":"2.8.3","commander":"14.0.3","node-forge":"1.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.5.1","vitest":"4.1.4","typescript":"6.0.2","@types/node":"25.6.0","@biomejs/biome":"2.4.4","@types/node-forge":"1.3.14"},"_npmOperationalInternal":{"tmp":"tmp/narsil-certutil_0.1.0_1788129752863_0.54990648039696","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@delali/narsil-certutil@0.2.0","bin":{"narsil-certutil":"dist/cli.mjs"},"bugs":{"url":"https://github.com/assetcorp/narsil/issues"},"dist":{"shasum":"19416df90d04e65cd2201ac375c716b265e8ab58","tarball":"https://registry.npmjs.org/@delali/narsil-certutil/-/narsil-certutil-0.2.0.tgz","fileCount":6,"integrity":"sha512-omoeM3j4Hcp/+5skQi3aGGCmuAj0xrlwXwV4l4qWCXZHKqpYOdRsghjs8Cl616R/tdu0IsfZp1VoZCjsn6t4/w==","signatures":[{"sig":"MEYCIQDubj9bg7I1TsDeZIsUzwCmJkZRedFcPQPimn9smQalqQIhAIa+BeBRf5Ld8ZOEsfL9pyFGuVVaqaxR/lUta4Tf2+iu","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCpclDkF7QUJbzYtoJm9JG5rIZ1z3FHlG5j4nLBh4bEBwIhAIVx8m63cdcUeuoMTl3gqr7HPyEuvdV9hlobTUf6OGuI"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@delali%2fnarsil-certutil@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":83569},"name":"@delali/narsil-certutil","type":"module","_from":"file:delali-narsil-certutil-0.2.0.tgz","author":{"url":"https://sondelali.com","name":"Delali"},"engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs"}},"license":"Apache-2.0","scripts":{"lint":"biome check src/","test":"vitest run","build":"tsup","typecheck":"pnpm run typecheck:src && pnpm run typecheck:tests","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck:src":"tsc --noEmit","typecheck:tests":"tsc --noEmit -p tsconfig.test.json"},"version":"0.2.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cfe25598-12d7-4d27-898a-ff3dc333e0be"}},"homepage":"https://narsil.sondelali.com","_resolved":"/tmp/199495c2c03023d7265f1cff08ddf752/delali-narsil-certutil-0.2.0.tgz","_integrity":"sha512-omoeM3j4Hcp/+5skQi3aGGCmuAj0xrlwXwV4l4qWCXZHKqpYOdRsghjs8Cl616R/tdu0IsfZp1VoZCjsn6t4/w==","repository":{"url":"git+https://github.com/assetcorp/narsil.git","type":"git","directory":"packages/certutil"},"_npmVersion":"11.19.0","description":"CLI tool for generating and managing TLS certificates for Narsil clusters.","directories":{},"maintainers":[{"name":"assetcorp","email":"delalify@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.21.0","dependencies":{"yaml":"2.8.3","commander":"14.0.3","node-forge":"1.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.5.1","vitest":"4.1.4","typescript":"6.0.2","@types/node":"25.6.0","@biomejs/biome":"2.4.4","@types/node-forge":"1.3.14"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/narsil-certutil_0.2.0_1790544259064_0.029703524762991762"}}},"time":{"created":"2026-08-30T22:42:32.706Z","modified":"2026-09-27T21:24:19.440Z","0.1.0":"2026-08-30T22:42:33.016Z","0.2.0":"2026-09-27T21:24:19.152Z"},"bugs":{"url":"https://github.com/assetcorp/narsil/issues"},"author":{"url":"https://sondelali.com","name":"Delali"},"license":"Apache-2.0","homepage":"https://narsil.sondelali.com","repository":{"url":"git+https://github.com/assetcorp/narsil.git","type":"git","directory":"packages/certutil"},"description":"CLI tool for generating and managing TLS certificates for Narsil clusters.","maintainers":[{"name":"assetcorp","email":"delalify@gmail.com"}],"readme":"# @delali/narsil-certutil\n\nCLI tool for generating and managing TLS certificates for Narsil clusters. Handles CA creation, node certificate signing, CSR generation for external CAs, certificate inspection, chain verification, and PEM/PKCS#12 format conversion.\n\n## Installation\n\n```bash\nnpm install -g @delali/narsil-certutil\n```\n\nOr use directly in a project:\n\n```bash\npnpm add -E @delali/narsil-certutil\n```\n\n## Quick start\n\nGenerate certificates for a three-node Narsil cluster in four commands:\n\n```bash\n# 1. Create a Certificate Authority\nnarsil-certutil ca --name \"My Narsil CA\" --out-dir ./certs\n\n# 2. Create a cluster config file\ncat > cluster.yaml << EOF\nnodes:\n  - cn: node1\n    ip: [10.0.0.1]\n    dns: [node1.cluster.local]\n  - cn: node2\n    ip: [10.0.0.2]\n    dns: [node2.cluster.local]\n  - cn: node3\n    ip: [10.0.0.3]\n    dns: [node3.cluster.local]\ndefaults:\n  days: 365\n  keySize: 2048\nEOF\n\n# 3. Generate all node certificates in one batch\nnarsil-certutil cert \\\n  --ca-cert ./certs/ca.crt \\\n  --ca-key ./certs/ca.key \\\n  --nodes cluster.yaml \\\n  --out-dir ./certs/nodes\n\n# 4. Verify each certificate\nnarsil-certutil verify \\\n  --cert ./certs/nodes/node1/node1.crt \\\n  --key ./certs/nodes/node1/node1.key \\\n  --ca-cert ./certs/ca.crt\n```\n\nThis produces:\n\n```\ncerts/\n  ca.crt\n  ca.key\n  nodes/\n    node1/\n      node1.crt\n      node1.key\n    node2/\n      node2.crt\n      node2.key\n    node3/\n      node3.crt\n      node3.key\n```\n\n## Commands\n\n### `narsil-certutil ca`\n\nGenerate a self-signed Certificate Authority.\n\n```bash\nnarsil-certutil ca --name \"Narsil CA\" --out-dir ./certs\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--name <name>` | required | CA common name |\n| `--days <n>` | 3650 | Validity period in days |\n| `--key-size <bits>` | 4096 | RSA key size (2048 or 4096) |\n| `--out-dir <dir>` | `.` | Output directory |\n| `--output <format>` | text | Output format (text or json) |\n| `--force` | false | Overwrite existing files |\n| `--dry-run` | false | Preview without writing |\n\n**Output files:** `ca.crt`, `ca.key`\n\n### `narsil-certutil cert`\n\nGenerate a node certificate signed by an existing CA. Supports single-node and batch modes.\n\n**Single node:**\n\n```bash\nnarsil-certutil cert \\\n  --cn node1 \\\n  --ca-cert ca.crt \\\n  --ca-key ca.key \\\n  --ip 10.0.0.1 \\\n  --dns node1.cluster.local \\\n  --out-dir ./certs\n```\n\n**Batch mode:**\n\n```bash\nnarsil-certutil cert \\\n  --ca-cert ca.crt \\\n  --ca-key ca.key \\\n  --nodes cluster.yaml \\\n  --out-dir ./certs/nodes\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--cn <name>` | | Node common name (required for single mode) |\n| `--ca-cert <path>` | required | Path to CA certificate |\n| `--ca-key <path>` | required | Path to CA private key |\n| `--ip <addresses...>` | | IP Subject Alternative Names |\n| `--dns <names...>` | | DNS Subject Alternative Names |\n| `--days <n>` | 365 | Validity period in days |\n| `--key-size <bits>` | 2048 | RSA key size |\n| `--out-dir <dir>` | `.` | Output directory |\n| `--nodes <path>` | | Cluster YAML/JSON for batch mode |\n| `--output <format>` | text | Output format |\n| `--force` | false | Overwrite existing files |\n| `--dry-run` | false | Preview without writing |\n\nAll node certificates include both `serverAuth` and `clientAuth` extended key usage, which is required for Narsil's mutual TLS (mTLS).\n\n**Output files:** `<cn>.crt`, `<cn>.key` (batch mode writes to `<out-dir>/<cn>/`)\n\n### `narsil-certutil csr`\n\nGenerate a Certificate Signing Request for organizations that require an external CA.\n\n**Single node:**\n\n```bash\nnarsil-certutil csr \\\n  --cn node1 \\\n  --ip 10.0.0.1 \\\n  --dns node1.cluster.local \\\n  --out-dir ./certs\n```\n\n**Batch mode:**\n\n```bash\nnarsil-certutil csr --nodes cluster.yaml --out-dir ./certs/csrs\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--cn <name>` | | Common name (required for single mode) |\n| `--ip <addresses...>` | | IP SANs |\n| `--dns <names...>` | | DNS SANs |\n| `--key-size <bits>` | 2048 | RSA key size |\n| `--out-dir <dir>` | `.` | Output directory |\n| `--nodes <path>` | | Cluster YAML/JSON for batch mode |\n| `--output <format>` | text | Output format |\n| `--force` | false | Overwrite existing files |\n| `--dry-run` | false | Preview without writing |\n\n**Output files:** `<cn>.csr`, `<cn>.key`\n\n**Workflow with an external CA:**\n\n1. Generate CSRs: `narsil-certutil csr --nodes cluster.yaml --out-dir ./csrs`\n2. Send `.csr` files to your organization's CA\n3. Receive back signed `.crt` files\n4. Verify: `narsil-certutil verify --cert node1.crt --key ./csrs/node1/node1.key --ca-cert company-ca.crt`\n5. Deploy the `.crt`, `.key`, and CA cert to each node\n\n### `narsil-certutil inspect`\n\nDisplay details of a PEM-encoded certificate, CSR, or private key.\n\n```bash\nnarsil-certutil inspect ./certs/node1.crt\n```\n\n```\nType:           certificate\nSubject:        CN=node1\nIssuer:         CN=Narsil CA\nValid from:     2026-04-12T00:00:00Z\nValid until:    2027-04-12T00:00:00Z\nExpires in:     364 days\nSerial:         a9f3e7...\nKey usage:      digitalSignature, keyEncipherment\nExt key usage:  serverAuth, clientAuth\nIP SANs:        10.0.0.1\nDNS SANs:       node1.cluster.local\nFingerprint:    SHA256:A9:F3:E7:...\nKey size:       2048-bit RSA\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `<file>` | required | Path to PEM file (positional argument) |\n| `--output <format>` | text | Output format |\n\n### `narsil-certutil verify`\n\nVerify a certificate's chain, key match, expiry, and mTLS readiness.\n\n```bash\nnarsil-certutil verify \\\n  --cert node1.crt \\\n  --key node1.key \\\n  --ca-cert ca.crt\n```\n\n```\n  pass  Certificate matches private key\n  pass  Certificate chain validates against CA\n  pass  Certificate not expired\n  pass  Key usage includes digitalSignature and keyEncipherment\n  pass  Ready for Narsil mTLS (serverAuth + clientAuth)\n```\n\nYou can run partial verification by omitting `--key` or `--ca-cert`:\n\n```bash\n# Chain only (no key match check)\nnarsil-certutil verify --cert node1.crt --ca-cert ca.crt\n\n# Key match only (no chain check)\nnarsil-certutil verify --cert node1.crt --key node1.key\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--cert <path>` | required | Certificate to verify |\n| `--key <path>` | | Private key to check match |\n| `--ca-cert <path>` | | CA certificate for chain validation |\n| `--output <format>` | text | Output format |\n\nExits with code 1 if any check fails.\n\n### `narsil-certutil convert`\n\nConvert between PEM and PKCS#12 (.p12/.pfx) formats. Useful for interoperability with Java keystores, Windows systems, and load balancers.\n\n**PEM to PKCS#12:**\n\n```bash\nnarsil-certutil convert \\\n  --cert node1.crt \\\n  --key node1.key \\\n  --ca-cert ca.crt \\\n  --to p12 \\\n  --p12-password changeit \\\n  --out-dir ./certs\n```\n\n**PKCS#12 to PEM:**\n\n```bash\nnarsil-certutil convert \\\n  --p12 node1.p12 \\\n  --to pem \\\n  --p12-password changeit \\\n  --out-dir ./certs\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--cert <path>` | | Certificate PEM (for p12 conversion) |\n| `--key <path>` | | Private key PEM (for p12 conversion) |\n| `--ca-cert <path>` | | CA cert PEM (included in p12 bundle) |\n| `--p12 <path>` | | PKCS#12 input file (for pem conversion) |\n| `--to <format>` | required | Target format: `pem` or `p12` |\n| `--p12-password <pw>` | | Password for PKCS#12 |\n| `--out-dir <dir>` | `.` | Output directory |\n| `--output <format>` | text | Output format |\n| `--force` | false | Overwrite existing files |\n\n## Cluster config file format\n\nThe `--nodes` flag accepts YAML or JSON files. YAML is recommended because it supports comments.\n\n**YAML:**\n\n```yaml\n# Production cluster certificate config\nnodes:\n  - cn: node1\n    ip: [10.0.0.1, 192.168.1.1]\n    dns: [node1.cluster.local, node1.search.internal]\n  - cn: node2\n    ip: [10.0.0.2]\n    dns: [node2.cluster.local]\n  - cn: node3\n    ip: [10.0.0.3]\n    dns: [node3.cluster.local]\n\ndefaults:\n  days: 365\n  keySize: 2048\n```\n\n**JSON equivalent:**\n\n```json\n{\n  \"nodes\": [\n    { \"cn\": \"node1\", \"ip\": [\"10.0.0.1\"], \"dns\": [\"node1.cluster.local\"] },\n    { \"cn\": \"node2\", \"ip\": [\"10.0.0.2\"], \"dns\": [\"node2.cluster.local\"] },\n    { \"cn\": \"node3\", \"ip\": [\"10.0.0.3\"], \"dns\": [\"node3.cluster.local\"] }\n  ],\n  \"defaults\": {\n    \"days\": 365,\n    \"keySize\": 2048\n  }\n}\n```\n\n**Fields:**\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `nodes` | yes | Array of node definitions |\n| `nodes[].cn` | yes | Common name for the node |\n| `nodes[].ip` | no | Array of IP addresses for SANs |\n| `nodes[].dns` | no | Array of DNS names for SANs |\n| `defaults.days` | no | Default validity period for all nodes |\n| `defaults.keySize` | no | Default RSA key size (2048 or 4096) |\n\n## JSON output mode\n\nAll commands support `--output json` for machine-readable output. The response uses a consistent envelope:\n\n**Success:**\n\n```json\n{\n  \"status\": \"success\",\n  \"data\": { ... },\n  \"error\": null,\n  \"metadata\": { \"duration_ms\": 14.2 }\n}\n```\n\n**Error:**\n\n```json\n{\n  \"status\": \"error\",\n  \"data\": null,\n  \"error\": {\n    \"code\": \"BAD_ARGUMENTS\",\n    \"message\": \"Either --cn or --nodes is required\",\n    \"suggestion\": \"Provide --cn for single cert or --nodes for batch mode\"\n  },\n  \"metadata\": { \"duration_ms\": 0.8 }\n}\n```\n\n## Environment variables\n\n| Variable | Equivalent flag | Description |\n|----------|----------------|-------------|\n| `NARSIL_CERT_OUT_DIR` | `--out-dir` | Default output directory |\n| `NARSIL_CA_CERT` | `--ca-cert` | Path to CA certificate |\n| `NARSIL_CA_KEY` | `--ca-key` | Path to CA private key |\n| `NARSIL_P12_PASSWORD` | `--p12-password` | PKCS#12 password |\n\nFlags take precedence over environment variables.\n\n## Exit codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | Success |\n| 1 | General error |\n| 2 | Invalid arguments |\n| 3 | Configuration problem |\n| 4 | File not found |\n| 5 | Permission denied |\n| 10 | Network or timeout failure |\n\n## Certificate rotation workflow\n\nCertificates expire. Here's how to rotate them without cluster downtime:\n\n```bash\n# 1. Check current cert expiry\nnarsil-certutil inspect /etc/narsil/tls/node1.crt --output json\n\n# 2. Generate a new certificate (self-signed CA)\nnarsil-certutil cert \\\n  --cn node1 \\\n  --ca-cert /etc/narsil/tls/ca.crt \\\n  --ca-key /etc/narsil/tls/ca.key \\\n  --ip 10.0.0.1 \\\n  --dns node1.cluster.local \\\n  --out-dir /tmp/rotation\n\n# Or generate a CSR for external CA\nnarsil-certutil csr \\\n  --cn node1 \\\n  --ip 10.0.0.1 \\\n  --dns node1.cluster.local \\\n  --out-dir /tmp/rotation\n\n# 3. Verify the new cert before deploying\nnarsil-certutil verify \\\n  --cert /tmp/rotation/node1.crt \\\n  --key /tmp/rotation/node1.key \\\n  --ca-cert /etc/narsil/tls/ca.crt\n\n# 4. Deploy (Narsil picks up new certs via file watch or SIGHUP)\ncp /tmp/rotation/node1.crt /etc/narsil/tls/node1.crt\ncp /tmp/rotation/node1.key /etc/narsil/tls/node1.key\n```\n\n## Programmatic API\n\nThe crypto functions are available as a library for use in scripts or other tools:\n\n```typescript\nimport {\n  generateCaCertificate,\n  generateNodeCertificate,\n  generateCsr,\n  pemToPkcs12,\n  pkcs12ToPem,\n  computeFingerprint,\n  detectPemType,\n  loadClusterConfig,\n} from '@delali/narsil-certutil'\n\nconst ca = generateCaCertificate({ name: 'My CA', days: 3650, keySize: 4096 })\n\nconst node = generateNodeCertificate({\n  caCertPem: ca.certPem,\n  caKeyPem: ca.keyPem,\n  cn: 'node1',\n  ipSans: ['10.0.0.1'],\n  dnsSans: ['node1.cluster.local'],\n  days: 365,\n  keySize: 2048,\n})\n```\n\n## License\n\nApache-2.0\n","readmeFilename":"README.md"}