|
| 1 | +# certMangler |
| 2 | + |
| 3 | +A certificate management tool that ingests TLS/SSL certificates and private keys in various formats, catalogs them in a SQLite database, and exports organized bundles in multiple output formats. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Multi-format ingestion** -- PEM, DER, PKCS#12, PKCS#8, encrypted PEM keys |
| 8 | +- **Automatic classification** -- Identifies root, intermediate, and leaf certificates |
| 9 | +- **Key type support** -- RSA, ECDSA, and Ed25519 |
| 10 | +- **Bundle export** -- Generates output in PEM (leaf, chain, fullchain, intermediates, root), PKCS#12, Kubernetes Secret YAML, JSON, YAML, and CSR formats |
| 11 | +- **Smart CSR generation** -- Produces renewal CSRs from existing certificates with configurable subject fields and intelligent SAN filtering |
| 12 | +- **SQLite catalog** -- Persistent or in-memory database indexed by Subject Key Identifier, serial number, and Authority Key Identifier |
| 13 | +- **Encrypted key handling** -- Tries user-supplied passwords plus common defaults (`""`, `"password"`, `"changeit"`) |
| 14 | + |
| 15 | +## Build |
| 16 | + |
| 17 | +Requires Go 1.25+ and a C compiler (for SQLite via cgo). |
| 18 | + |
| 19 | +```sh |
| 20 | +go build -o certmangler main.go |
| 21 | +``` |
| 22 | + |
| 23 | +Or use the included build script: |
| 24 | + |
| 25 | +```sh |
| 26 | +./tools/build.sh |
| 27 | +``` |
| 28 | + |
| 29 | +## Usage |
| 30 | + |
| 31 | +``` |
| 32 | +certmangler -path <input> [flags] |
| 33 | +``` |
| 34 | + |
| 35 | +### Flags |
| 36 | + |
| 37 | +| Flag | Default | Description | |
| 38 | +|---|---|---| |
| 39 | +| `-path` | *(required)* | Path to a certificate file or directory, or `-` for stdin | |
| 40 | +| `-export` | `false` | Export certificate bundles after ingestion | |
| 41 | +| `-force` | `false` | Export bundles even for untrusted certificates | |
| 42 | +| `-out` | `./bundles` | Output directory for exported bundles | |
| 43 | +| `-bundles-config` | `./bundles.yaml` | Path to bundle configuration file | |
| 44 | +| `-dbpath` | *(empty)* | SQLite database path (empty = in-memory) | |
| 45 | +| `-passwords` | *(empty)* | Comma-separated list of passwords for encrypted keys | |
| 46 | +| `-password-file` | *(empty)* | File containing passwords, one per line | |
| 47 | +| `-loglevel` | `debug` | Log level: `debug`, `info`, `warning`, `error`, `critical`, `fatal` | |
| 48 | + |
| 49 | +### Examples |
| 50 | + |
| 51 | +Ingest a directory of certificates and keys: |
| 52 | + |
| 53 | +```sh |
| 54 | +./certmangler -path ./certs/ |
| 55 | +``` |
| 56 | + |
| 57 | +Ingest and export bundles with a persistent database: |
| 58 | + |
| 59 | +```sh |
| 60 | +./certmangler -path ./certs/ -export -dbpath certs.db -out ./bundles |
| 61 | +``` |
| 62 | + |
| 63 | +Read from stdin: |
| 64 | + |
| 65 | +```sh |
| 66 | +cat server.pem | ./certmangler -path - |
| 67 | +``` |
| 68 | + |
| 69 | +Provide passwords for encrypted PKCS#12 or PEM files: |
| 70 | + |
| 71 | +```sh |
| 72 | +./certmangler -path ./certs/ -passwords "secret1,secret2" -password-file extra_passwords.txt |
| 73 | +``` |
| 74 | + |
| 75 | +## Bundle Configuration |
| 76 | + |
| 77 | +Bundles are defined in a YAML file that maps certificate Common Names to named bundles. An optional `defaultSubject` provides fallback X.509 subject fields for CSR generation. |
| 78 | + |
| 79 | +```yaml |
| 80 | +defaultSubject: |
| 81 | + country: [US] |
| 82 | + province: [California] |
| 83 | + locality: [San Diego] |
| 84 | + organization: [Company, Inc.] |
| 85 | + organizationalUnit: [DevOps] |
| 86 | + |
| 87 | +bundles: |
| 88 | + - bundleName: examplecom-tls |
| 89 | + commonNames: |
| 90 | + - '*.example.com' |
| 91 | + - example.com |
| 92 | + custodian: devops@example.com |
| 93 | + usage: [k8s] |
| 94 | + |
| 95 | + - bundleName: exampleio-tls |
| 96 | + commonNames: |
| 97 | + - '*.example.io' |
| 98 | + - example.io |
| 99 | + custodian: devops@example.com |
| 100 | + usage: [k8s] |
| 101 | + subject: # overrides defaultSubject for this bundle |
| 102 | + country: [US] |
| 103 | + province: [Virginia] |
| 104 | + locality: [Arlington] |
| 105 | + organization: [Other Corp.] |
| 106 | + organizationalUnit: [Engineering] |
| 107 | +``` |
| 108 | +
|
| 109 | +Bundles without an explicit `subject` block inherit from `defaultSubject`. |
| 110 | + |
| 111 | +## Output Files |
| 112 | + |
| 113 | +When `-export` is set, each bundle produces the following files under `<out>/<bundleName>/`: |
| 114 | + |
| 115 | +| File | Contents | |
| 116 | +|---|---| |
| 117 | +| `<cn>.pem` | Leaf certificate | |
| 118 | +| `<cn>.chain.pem` | Leaf + intermediates | |
| 119 | +| `<cn>.fullchain.pem` | Leaf + intermediates + root | |
| 120 | +| `<cn>.intermediates.pem` | Intermediate certificates | |
| 121 | +| `<cn>.root.pem` | Root certificate | |
| 122 | +| `<cn>.key` | Private key (PEM, mode 0600) | |
| 123 | +| `<cn>.p12` | PKCS#12 archive (password: `changeit`, mode 0600) | |
| 124 | +| `<cn>.k8s.yaml` | Kubernetes `kubernetes.io/tls` Secret (mode 0600) | |
| 125 | +| `<cn>.json` | Certificate metadata | |
| 126 | +| `<cn>.yaml` | Certificate and key metadata | |
| 127 | +| `<cn>.csr` | Certificate Signing Request | |
| 128 | +| `<cn>.csr.json` | CSR details (subject, SANs, key algorithm) | |
| 129 | + |
| 130 | +Wildcard characters in the CN are replaced with `_` in filenames (e.g., `*.example.com` becomes `_.example.com`). When multiple certificates match the same bundle, the newest gets the bare name and older ones receive a `_<date>_<serial>` suffix. |
| 131 | + |
| 132 | +## How It Works |
| 133 | + |
| 134 | +``` |
| 135 | +Input files/stdin |
| 136 | + | |
| 137 | + v |
| 138 | + Format detection (PEM vs DER) |
| 139 | + | |
| 140 | + v |
| 141 | + Parse certificates, keys, CSRs |
| 142 | + (handles PKCS#12, encrypted PEM, PKCS#8, SEC1, Ed25519) |
| 143 | + | |
| 144 | + v |
| 145 | + Store in SQLite (certificates + keys indexed by SKI) |
| 146 | + | |
| 147 | + v |
| 148 | + [if -export] Match keys to certs, build chains via CFSSL, |
| 149 | + write all output formats per bundle |
| 150 | +``` |
| 151 | +
|
| 152 | +Expired certificates are skipped during ingestion. Root certificates missing an Authority Key Identifier use their own Subject Key Identifier as a fallback. Certificate-to-bundle matching is performed by comparing the certificate's Common Name against the `commonNames` list in each bundle configuration. |
| 153 | +
|
| 154 | +## License |
| 155 | +
|
| 156 | +[MIT](LICENSE) |
0 commit comments