Skip to content

Commit 10601d4

Browse files
committed
feat: adds templating, positional arg for common name and other improvements.
Signed-off-by: ianhundere <[email protected]>
1 parent 3d56169 commit 10601d4

File tree

10 files changed

+980
-3228
lines changed

10 files changed

+980
-3228
lines changed

cmd/certificate_maker/certificate_maker.go

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"fmt"
2323
"os"
24+
"strings"
2425
"time"
2526

2627
"github.com/sigstore/timestamp-authority/pkg/certmaker"
@@ -44,9 +45,14 @@ var (
4445
}
4546

4647
createCmd = &cobra.Command{
47-
Use: "create",
48+
Use: "create [common-name]",
4849
Short: "Create certificate chain",
49-
RunE: runCreate,
50+
Long: `Create a certificate chain with the specified common name.
51+
The common name will be used as the Subject Common Name for the certificates.
52+
If no common name is provided, the values from the templates will be used.
53+
Example: tsa-certificate-maker create "https://timestamp.example.com"`,
54+
Args: cobra.RangeArgs(0, 1),
55+
RunE: runCreate,
5056
}
5157
)
5258

@@ -65,72 +71,83 @@ func mustBindEnv(key, envVar string) {
6571
func init() {
6672
log.ConfigureLogger("prod")
6773

74+
viper.AutomaticEnv()
75+
viper.SetEnvPrefix("")
76+
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
77+
78+
mustBindEnv("kms-type", "KMS_TYPE")
79+
mustBindEnv("aws-region", "AWS_REGION")
80+
mustBindEnv("azure-tenant-id", "AZURE_TENANT_ID")
81+
mustBindEnv("gcp-credentials-file", "GCP_CREDENTIALS_FILE")
82+
mustBindEnv("vault-token", "VAULT_TOKEN")
83+
mustBindEnv("vault-address", "VAULT_ADDR")
84+
mustBindEnv("root-key-id", "KMS_ROOT_KEY_ID")
85+
mustBindEnv("intermediate-key-id", "KMS_INTERMEDIATE_KEY_ID")
86+
mustBindEnv("leaf-key-id", "KMS_LEAF_KEY_ID")
87+
6888
rootCmd.AddCommand(createCmd)
69-
rootCmd.SetVersionTemplate("{{.Version}}\n")
7089

7190
// KMS provider flags
72-
createCmd.Flags().String("kms-type", "", "KMS provider type (awskms, gcpkms, azurekms, hashivault)")
91+
createCmd.Flags().String("kms-type", "", "KMS provider type")
7392
createCmd.Flags().String("aws-region", "", "AWS KMS region")
7493
createCmd.Flags().String("azure-tenant-id", "", "Azure KMS tenant ID")
7594
createCmd.Flags().String("gcp-credentials-file", "", "Path to credentials file for GCP KMS")
7695
createCmd.Flags().String("vault-token", "", "HashiVault token")
7796
createCmd.Flags().String("vault-address", "", "HashiVault server address")
7897

7998
// Root certificate flags
80-
createCmd.Flags().String("root-template", "pkg/certmaker/templates/root-template.json", "Path to root certificate template")
8199
createCmd.Flags().String("root-key-id", "", "KMS key identifier for root certificate")
100+
createCmd.Flags().String("root-template", "", "Path to root certificate template (optional)")
82101
createCmd.Flags().String("root-cert", "root.pem", "Output path for root certificate")
83102

84103
// Intermediate certificate flags
85-
createCmd.Flags().String("intermediate-template", "pkg/certmaker/templates/intermediate-template.json", "Path to intermediate certificate template")
86104
createCmd.Flags().String("intermediate-key-id", "", "KMS key identifier for intermediate certificate")
105+
createCmd.Flags().String("intermediate-template", "", "Path to intermediate certificate template (optional)")
87106
createCmd.Flags().String("intermediate-cert", "intermediate.pem", "Output path for intermediate certificate")
88107

89108
// Leaf certificate flags
90-
createCmd.Flags().String("leaf-template", "pkg/certmaker/templates/leaf-template.json", "Path to leaf certificate template")
91109
createCmd.Flags().String("leaf-key-id", "", "KMS key identifier for leaf certificate")
110+
createCmd.Flags().String("leaf-template", "", "Path to leaf certificate template (optional)")
92111
createCmd.Flags().String("leaf-cert", "leaf.pem", "Output path for leaf certificate")
93112

94-
// Bind flags to viper
113+
// Lifetime flags
114+
createCmd.Flags().Duration("root-lifetime", 87600*time.Hour, "Root certificate lifetime")
115+
createCmd.Flags().Duration("intermediate-lifetime", 43800*time.Hour, "Intermediate certificate lifetime")
116+
createCmd.Flags().Duration("leaf-lifetime", 8760*time.Hour, "Leaf certificate lifetime")
117+
95118
mustBindPFlag("kms-type", createCmd.Flags().Lookup("kms-type"))
96119
mustBindPFlag("aws-region", createCmd.Flags().Lookup("aws-region"))
97120
mustBindPFlag("azure-tenant-id", createCmd.Flags().Lookup("azure-tenant-id"))
98121
mustBindPFlag("gcp-credentials-file", createCmd.Flags().Lookup("gcp-credentials-file"))
99122
mustBindPFlag("vault-token", createCmd.Flags().Lookup("vault-token"))
100123
mustBindPFlag("vault-address", createCmd.Flags().Lookup("vault-address"))
101-
102-
mustBindPFlag("root-template", createCmd.Flags().Lookup("root-template"))
103124
mustBindPFlag("root-key-id", createCmd.Flags().Lookup("root-key-id"))
125+
mustBindPFlag("root-template", createCmd.Flags().Lookup("root-template"))
104126
mustBindPFlag("root-cert", createCmd.Flags().Lookup("root-cert"))
105-
106-
mustBindPFlag("intermediate-template", createCmd.Flags().Lookup("intermediate-template"))
107127
mustBindPFlag("intermediate-key-id", createCmd.Flags().Lookup("intermediate-key-id"))
128+
mustBindPFlag("intermediate-template", createCmd.Flags().Lookup("intermediate-template"))
108129
mustBindPFlag("intermediate-cert", createCmd.Flags().Lookup("intermediate-cert"))
109-
110-
mustBindPFlag("leaf-template", createCmd.Flags().Lookup("leaf-template"))
111130
mustBindPFlag("leaf-key-id", createCmd.Flags().Lookup("leaf-key-id"))
131+
mustBindPFlag("leaf-template", createCmd.Flags().Lookup("leaf-template"))
112132
mustBindPFlag("leaf-cert", createCmd.Flags().Lookup("leaf-cert"))
113-
114-
// Bind environment variables
115-
mustBindEnv("kms-type", "KMS_TYPE")
116-
mustBindEnv("aws-region", "AWS_REGION")
117-
mustBindEnv("azure-tenant-id", "AZURE_TENANT_ID")
118-
mustBindEnv("gcp-credentials-file", "GCP_CREDENTIALS_FILE")
119-
mustBindEnv("vault-token", "VAULT_TOKEN")
120-
mustBindEnv("vault-address", "VAULT_ADDR")
121-
122-
mustBindEnv("root-key-id", "KMS_ROOT_KEY_ID")
123-
mustBindEnv("intermediate-key-id", "KMS_INTERMEDIATE_KEY_ID")
124-
mustBindEnv("leaf-key-id", "KMS_LEAF_KEY_ID")
133+
mustBindPFlag("root-lifetime", createCmd.Flags().Lookup("root-lifetime"))
134+
mustBindPFlag("intermediate-lifetime", createCmd.Flags().Lookup("intermediate-lifetime"))
135+
mustBindPFlag("leaf-lifetime", createCmd.Flags().Lookup("leaf-lifetime"))
125136
}
126137

127-
func runCreate(_ *cobra.Command, _ []string) error {
128-
defer func() { rootCmd.SilenceUsage = true }()
138+
func runCreate(_ *cobra.Command, args []string) error {
129139
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
130140
defer cancel()
131141

142+
// Get common name from args if provided, otherwise templates used
143+
commonName := viper.GetString("common-name")
144+
if len(args) > 0 {
145+
commonName = args[0]
146+
}
147+
132148
// Build KMS config from flags and environment
133149
config := certmaker.KMSConfig{
150+
CommonName: commonName,
134151
Type: viper.GetString("kms-type"),
135152
RootKeyID: viper.GetString("root-key-id"),
136153
IntermediateKeyID: viper.GetString("intermediate-key-id"),
@@ -146,7 +163,7 @@ func runCreate(_ *cobra.Command, _ []string) error {
146163
}
147164
case "gcpkms":
148165
if gcpCredsFile := viper.GetString("gcp-credentials-file"); gcpCredsFile != "" {
149-
// Check if credentials file exists before trying to use it
166+
// Check if gcp creds exists
150167
if _, err := os.Stat(gcpCredsFile); err != nil {
151168
if os.IsNotExist(err) {
152169
return fmt.Errorf("failed to initialize KMS: credentials file not found: %s", gcpCredsFile)
@@ -173,24 +190,32 @@ func runCreate(_ *cobra.Command, _ []string) error {
173190
return fmt.Errorf("failed to initialize KMS: %w", err)
174191
}
175192

176-
// Validate template paths
193+
// Validate template paths if provided
177194
rootTemplate := viper.GetString("root-template")
178195
leafTemplate := viper.GetString("leaf-template")
179-
intermediateTemplate := viper.GetString("intermediate-template")
180196

181-
if err := certmaker.ValidateTemplatePath(rootTemplate); err != nil {
182-
return fmt.Errorf("root template error: %w", err)
183-
}
184-
if err := certmaker.ValidateTemplatePath(leafTemplate); err != nil {
185-
return fmt.Errorf("leaf template error: %w", err)
197+
if rootTemplate != "" {
198+
if err := certmaker.ValidateTemplate(rootTemplate, nil, "root"); err != nil {
199+
return fmt.Errorf("root template error: %w", err)
200+
}
186201
}
187-
if viper.GetString("intermediate-key-id") != "" {
188-
if err := certmaker.ValidateTemplatePath(intermediateTemplate); err != nil {
189-
return fmt.Errorf("intermediate template error: %w", err)
202+
if leafTemplate != "" {
203+
if err := certmaker.ValidateTemplate(leafTemplate, nil, "leaf"); err != nil {
204+
return fmt.Errorf("leaf template error: %w", err)
190205
}
191206
}
192207

193-
return certmaker.CreateCertificates(km, config, viper.GetString("root-template"), viper.GetString("leaf-template"), viper.GetString("root-cert"), viper.GetString("leaf-cert"), viper.GetString("intermediate-key-id"), viper.GetString("intermediate-template"), viper.GetString("intermediate-cert"))
208+
return certmaker.CreateCertificates(km, config,
209+
rootTemplate,
210+
leafTemplate,
211+
viper.GetString("root-cert"),
212+
viper.GetString("leaf-cert"),
213+
viper.GetString("intermediate-key-id"),
214+
viper.GetString("intermediate-template"),
215+
viper.GetString("intermediate-cert"),
216+
viper.GetDuration("root-lifetime"),
217+
viper.GetDuration("intermediate-lifetime"),
218+
viper.GetDuration("leaf-lifetime"))
194219
}
195220

196221
func main() {

cmd/certificate_maker/certificate_maker_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ func TestGetConfigValue(t *testing.T) {
3636
envValue string
3737
want string
3838
}{
39-
// KMS provider flags
4039
{
4140
name: "get KMS type from flag",
4241
flag: "kms-type",
@@ -85,7 +84,6 @@ func TestGetConfigValue(t *testing.T) {
8584
envValue: "http://vault:8200",
8685
want: "http://vault:8200",
8786
},
88-
// Root certificate flags
8987
{
9088
name: "get root key ID from env",
9189
flag: "root-key-id",
@@ -94,7 +92,6 @@ func TestGetConfigValue(t *testing.T) {
9492
envValue: "root-key-123",
9593
want: "root-key-123",
9694
},
97-
// Intermediate certificate flags
9895
{
9996
name: "get intermediate key ID from env",
10097
flag: "intermediate-key-id",
@@ -103,7 +100,6 @@ func TestGetConfigValue(t *testing.T) {
103100
envValue: "intermediate-key-123",
104101
want: "intermediate-key-123",
105102
},
106-
// Leaf certificate flags
107103
{
108104
name: "get leaf key ID from env",
109105
flag: "leaf-key-id",
@@ -290,7 +286,7 @@ func TestRunCreate(t *testing.T) {
290286
"--leaf-template", leafTmplPath,
291287
},
292288
wantError: true,
293-
errMsg: "error getting root public key: getting public key: operation error KMS: GetPublicKey",
289+
errMsg: "leaf template error: leaf certificate must have a parent",
294290
},
295291
{
296292
name: "HashiVault KMS without token",
@@ -425,7 +421,7 @@ func TestCreateCommand(t *testing.T) {
425421
"--leaf-template", leafTmplPath,
426422
},
427423
wantError: true,
428-
errMsg: "error getting root public key: getting public key: operation error KMS: GetPublicKey",
424+
errMsg: "leaf template error: leaf certificate must have a parent",
429425
},
430426
}
431427

docs/certificate-maker.md

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ This tool creates root, intermediate (optional), and leaf certificates for Times
55
- Two-level chain (root -> leaf)
66
- Three-level chain (root -> intermediate -> leaf)
77

8-
Relies on [x509util](https://pkg.go.dev/go.step.sm/crypto/x509util) which builds X.509 certificates from JSON templates.
8+
Relies on [x509util](https://pkg.go.dev/go.step.sm/crypto/x509util) which builds X.509 certificates from JSON templates. The tool includes embedded default templates that are compiled into the binary, making it ready to use without external template files.
99

1010
## Requirements
1111

12-
- Access to one of the supported KMS providers (AWS, Google Cloud, Azure)
12+
- Access to one of the supported KMS providers (AWS, Google Cloud, Azure, HashiCorp Vault)
1313
- Pre-existing KMS keys (the tool uses existing keys and does not create new ones)
1414

1515
## Local Development
@@ -27,6 +27,14 @@ The tool can be configured using either command-line flags or environment variab
2727

2828
### Command-Line Interface
2929

30+
The `create` command accepts an optional positional argument for the common name:
31+
32+
```bash
33+
./bin/tsa-certificate-maker create [common-name]
34+
```
35+
36+
If no common name is provided, the values from the templates will be used.
37+
3038
Available flags:
3139

3240
- `--kms-type`: KMS provider type (awskms, gcpkms, azurekms, hashivault)
@@ -44,6 +52,9 @@ Available flags:
4452
- `--intermediate-key-id`: KMS key identifier for intermediate certificate
4553
- `--intermediate-template`: Path to intermediate certificate template
4654
- `--intermediate-cert`: Output path for intermediate certificate
55+
- `--root-lifetime`: Root certificate lifetime (default: 87600h, 10 years)
56+
- `--intermediate-lifetime`: Intermediate certificate lifetime (default: 43800h, 5 years)
57+
- `--leaf-lifetime`: Leaf certificate lifetime (default: 8760h, 1 year)
4758

4859
### Environment Variables
4960

@@ -52,21 +63,20 @@ Available flags:
5263
- `KMS_INTERMEDIATE_KEY_ID`: Key identifier for intermediate certificate
5364
- `LEAF_KEY_ID`: Key identifier for leaf certificate
5465
- `AWS_REGION`: AWS Region (required for AWS KMS)
55-
- `KMS_VAULT_NAME`: Azure Key Vault name
5666
- `AZURE_TENANT_ID`: Azure tenant ID
5767
- `GCP_CREDENTIALS_FILE`: Path to credentials file (for Google Cloud KMS)
5868
- `VAULT_ADDR`: HashiCorp Vault address
5969
- `VAULT_TOKEN`: HashiCorp Vault token
6070

6171
### Certificate Templates
6272

63-
The tool uses JSON templates to define certificate properties:
73+
The embedded templates are located in `pkg/certmaker/templates/` in the source code and are compiled into the binary. You can override these defaults by providing your own template files using:
6474

65-
- `root-template.json`: Defines root CA certificate properties
66-
- `intermediate-template.json`: Defines intermediate CA certificate properties (when using --intermediate-key-id)
67-
- `leaf-template.json`: Defines leaf certificate properties
75+
- `--root-template`: Custom root CA template
76+
- `--intermediate-template`: Custom intermediate CA template
77+
- `--leaf-template`: Custom leaf template
6878

69-
Templates are located in `pkg/certmaker/templates/`.
79+
If no custom templates are provided via flags, the tool will automatically use the embedded defaults which are designed to work with TSA's certificate requirements as long as the intended common name is used as a positional argument.
7080

7181
Note: Templates use ASN.1/OID format for timestamping-specific extensions.
7282

@@ -96,7 +106,7 @@ export KMS_INTERMEDIATE_KEY_ID=projects/PROJECT_ID/locations/LOCATION/keyRings/K
96106
```shell
97107
export KMS_TYPE=azurekms
98108
export ROOT_KEY_ID=azurekms:name=root-key;vault=tsa-keys
99-
export KMS_INTERMEDIATE_KEY_ID=azurekms:name=leaf-key;vault=fulcio-keys
109+
export KMS_INTERMEDIATE_KEY_ID=azurekms:name=leaf-key;vault=tsa-keys
100110
export LEAF_KEY_ID=azurekms:name=leaf-key;vault=tsa-keys
101111
export AZURE_TENANT_ID=83j229-83j229-83j229-83j229-83j229
102112
```
@@ -242,54 +252,65 @@ Certificate:
242252
Example with AWS KMS:
243253

244254
```bash
245-
tsa-certificate-maker create \
255+
tsa-certificate-maker create "https://tsa.example.com" \
246256
--kms-type awskms \
247257
--aws-region us-east-1 \
248258
--root-key-id alias/tsa-root \
249259
--leaf-key-id alias/tsa-leaf \
250260
--root-template pkg/certmaker/templates/root-template.json \
251-
--leaf-template pkg/certmaker/templates/leaf-template.json
261+
--leaf-template pkg/certmaker/templates/leaf-template.json \
262+
--root-lifetime 87600h \
263+
--leaf-lifetime 8760h
252264
```
253265

254266
Example with Azure KMS:
255267

256268
```bash
257-
tsa-certificate-maker create \
269+
tsa-certificate-maker create "https://tsa.example.com" \
258270
--kms-type azurekms \
259271
--azure-tenant-id 1b4a4fed-fed8-4823-a8a0-3d5cea83d122 \
260272
--root-key-id "azurekms:name=sigstore-key;vault=sigstore-key" \
261273
--leaf-key-id "azurekms:name=sigstore-key-intermediate;vault=sigstore-key" \
262-
--intermediate-key-id "azurekms:name=sigstore-key-intermediate;vault=sigstore-key \
274+
--intermediate-key-id "azurekms:name=sigstore-key-intermediate;vault=sigstore-key" \
263275
--root-cert root.pem \
264276
--leaf-cert leaf.pem \
265-
--intermediate-cert intermediate.pem
277+
--intermediate-cert intermediate.pem \
278+
--root-lifetime 87600h \
279+
--intermediate-lifetime 43800h \
280+
--leaf-lifetime 8760h
266281
```
267282

268283
Example with GCP KMS:
269284

270285
```bash
271-
tsa-certificate-maker create \
286+
tsa-certificate-maker create "https://tsa.example.com" \
272287
--kms-type gcpkms \
273-
---gcp-credentials-file ~/.config/gcloud/application_default_credentials.json \
274-
--root-key-id projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/fulcio-key1/cryptoKeyVersions/<version> \
275-
--intermediate-key-id projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/fulcio-key1/cryptoKeyVersions/<version> \
276-
--leaf-key-id projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/fulcio-key1/cryptoKeyVersions/<version> \
288+
--gcp-credentials-file ~/.config/gcloud/application_default_credentials.json \
289+
--root-key-id projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/tsa-key1/cryptoKeyVersions/<version> \
290+
--intermediate-key-id projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/tsa-key1/cryptoKeyVersions/<version> \
291+
--leaf-key-id projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/tsa-key1/cryptoKeyVersions/<version> \
277292
--root-cert root.pem \
278293
--leaf-cert leaf.pem \
279-
--intermediate-cert intermediate.pem
294+
--intermediate-cert intermediate.pem \
295+
--root-lifetime 87600h \
296+
--intermediate-lifetime 43800h \
297+
--leaf-lifetime 8760h
280298
```
281299

282300
Example with HashiCorp Vault KMS:
283301

284302
```bash
285-
tsa-certificate-maker create \
303+
tsa-certificate-maker create "https://tsa.example.com" \
286304
--kms-type hashivault \
287305
--vault-address http://vault:8200 \
288306
--vault-token token \
289307
--root-key-id "transit/keys/root-key" \
290308
--leaf-key-id "transit/keys/leaf-key" \
291-
--intermediate-key-id "transit/keys/intermediate-key \
309+
--intermediate-key-id "transit/keys/intermediate-key" \
292310
--root-cert root.pem \
293311
--leaf-cert leaf.pem \
294-
--intermediate-cert intermediate.pem
312+
--intermediate-cert intermediate.pem \
313+
--root-lifetime 87600h \
314+
--intermediate-lifetime 43800h \
315+
--leaf-lifetime 8760h
295316
```

0 commit comments

Comments
 (0)