Skip to content

Commit d5e8ce5

Browse files
committed
Update Go version and dependencies; add README with usage instructions and features
1 parent dd76443 commit d5e8ce5

3 files changed

Lines changed: 170 additions & 14 deletions

File tree

README.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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)

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module github.com/danielewood/certmangler
22

3-
go 1.24.1
3+
go 1.25.1
44

55
require (
66
github.com/cloudflare/cfssl v1.6.5
77
github.com/jmoiron/sqlx v1.4.0
8-
github.com/mattn/go-sqlite3 v1.14.28
9-
golang.org/x/crypto v0.39.0
8+
github.com/mattn/go-sqlite3 v1.14.33
9+
golang.org/x/crypto v0.48.0
1010
gopkg.in/yaml.v3 v3.0.1
11-
software.sslmate.com/src/go-pkcs12 v0.5.0
11+
software.sslmate.com/src/go-pkcs12 v0.7.0
1212
)
1313

1414
require (

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
2222
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
2323
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
2424
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
25-
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
26-
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
25+
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
26+
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
2727
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
2828
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
2929
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
@@ -71,8 +71,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
7171
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
7272
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
7373
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
74-
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
75-
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
74+
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
75+
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
7676
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
7777
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
7878
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -91,8 +91,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
9191
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
9292
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
9393
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
94-
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
95-
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
94+
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
95+
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
9696
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
9797
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
9898
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -139,8 +139,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
139139
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
140140
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
141141
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
142-
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
143-
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
142+
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
143+
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
144144
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
145145
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
146146
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -157,5 +157,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
157157
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
158158
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
159159
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
160-
software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M=
161-
software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
160+
software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0=
161+
software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=

0 commit comments

Comments
 (0)