Repository: sigstore/fulcio
Pull request #1927 (2025-01-27 02:50:53)

Bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.25.1 to 2.26.0
Bumps [github.com/grpc-ecosystem/grpc-gateway/v2](https://github.com/grpc-ecosystem/grpc-gateway) from 2.25.1 to 2.26.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/grpc-ecosystem/grpc-gateway/releases">github.com/grpc-ecosystem/grpc-gateway/v2's releases</a>.</em></p>
<blockquote>
<h2>v2.26.0</h2>
<h2>What's Changed</h2>
<ul>
<li>fix(deps): Relax the minimum required go version further by <a href="https://github.com/bobrik"><code>@​bobrik</code></a> in <a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/pull/5094">grpc-ecosystem/grpc-gateway#5094</a></li>
<li>protoc-gen-openapiv2: Do not add invisible enum values as default by <a href="https://github.com/navruzm"><code>@​navruzm</code></a> in <a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/pull/5129">grpc-ecosystem/grpc-gateway#5129</a></li>
<li>Only write Content-Length if the runtime.WithWriteContentLength() option is specified by <a href="https://github.com/joshgarnett"><code>@​joshgarnett</code></a> in <a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/pull/5151">grpc-ecosystem/grpc-gateway#5151</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/bobrik"><code>@​bobrik</code></a> made their first contribution in <a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/pull/5094">grpc-ecosystem/grpc-gateway#5094</a></li>
<li><a href="https://github.com/navruzm"><code>@​navruzm</code></a> made their first contribution in <a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/pull/5129">grpc-ecosystem/grpc-gateway#5129</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.25.1...v2.26.0">https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.25.1...v2.26.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/f042173773f71f91be98f9e1266c88163dc769a2"><code>f042173</code></a> build(deps): bump just-the-docs from 0.10.0 to 0.10.1 in /docs (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5112">#5112</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/3098c6a4d4519f7bedd152fe9998a2082fe7150c"><code>3098c6a</code></a> chore(deps): update dependency com_github_bazelbuild_buildtools to v8 (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5126">#5126</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/c51073ca7741f1e2eee8c48752fc6c095b0aa93b"><code>c51073c</code></a> chore(deps): update googleapis digest to 318818b (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5156">#5156</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/5dfd063420c85c1114f29c9a04153f2f48a234da"><code>5dfd063</code></a> Only write Content-Length if the runtime.WithWriteContentLength() option is s...</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/e1364b509dcff8f2343a296416c81a81f6d6ed35"><code>e1364b5</code></a> protoc-gen-openapiv2: Do not add invisible enum values as default (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5129">#5129</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/3b7b760d3be07c3db3ea85e2516385d923a24320"><code>3b7b760</code></a> chore(deps): update googleapis digest to 5c2ccb6 (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5155">#5155</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/0663fba585c3055949e8d18bff37dc5bb105416d"><code>0663fba</code></a> chore(deps): update googleapis digest to 07737e5 (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5154">#5154</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/3ef2ee05013ace94551688e930c66d2af439c026"><code>3ef2ee0</code></a> chore(deps): update dependency bazel to v8.0.1 (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5153">#5153</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/12a233b54474d68de036d7a2276aaac31c98fc3d"><code>12a233b</code></a> chore(deps): update googleapis digest to 3776db1 (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5152">#5152</a>)</li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/commit/4da16532c63fe8d2f0cbc1d7f7622b263f8ba257"><code>4da1653</code></a> chore(deps): update dependency rules_python to v1.1.0 (<a href="https://redirect.github.com/grpc-ecosystem/grpc-gateway/issues/5150">#5150</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.25.1...v2.26.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/grpc-ecosystem/grpc-gateway/v2&package-manager=go_modules&previous-version=2.25.1&new-version=2.26.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

cpanato: @dependabot rebase
cpanato: @dependabot rebase

Patch:
diff --git a/.github/workflows/container-build.yml b/.github/workflows/container-build.yml
index 3f87eed..462f2e9 100644
--- a/.github/workflows/container-build.yml
+++ b/.github/workflows/container-build.yml
@@ -37,7 +37,7 @@ jobs:
         with:
           persist-credentials: false
 
-      - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
+      - uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
 
       - name: Extract version of Go to use
         run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV
@@ -53,7 +53,7 @@ jobs:
       - uses: ko-build/setup-ko@d982fec422852203cfb2053a8ec6ad302280d04d # v0.8
 
       - name: Set up Cloud SDK
-        uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7
+        uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8
         with:
           workload_identity_provider: 'projects/498091336538/locations/global/workloadIdentityPools/githubactions/providers/sigstore-fulcio'
           service_account: 'github-actions-fulcio@projectsigstore.iam.gserviceaccount.com'
diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml
index 8511be7..58b537d 100644
--- a/.github/workflows/validate-release.yml
+++ b/.github/workflows/validate-release.yml
@@ -28,14 +28,14 @@ jobs:
   check-signature:
     runs-on: ubuntu-latest
     container:
-      image: ghcr.io/sigstore/cosign/cosign:v2.4.1-dev@sha256:a1bb112f1758703aa1d222bf30b9655d04cf196c0b7feaf3479d1222c2283590
+      image: ghcr.io/sigstore/cosign/cosign:v2.4.2-dev@sha256:b69af124cb744c22eba955ebaf9514b42b1556811211e1cd744331350ccd815a
 
     steps:
       - name: Check Signature
         run: |
-          cosign verify ghcr.io/gythialy/golang-cross:v1.23.4-0@sha256:fac6f9675e3e3a4ccc2b8cdab87b907cabae3a9c4e046d16816b9876315005e4 \
+          cosign verify ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05 \
           --certificate-oidc-issuer https://token.actions.githubusercontent.com \
-          --certificate-identity "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.23.4-0"
+          --certificate-identity "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.23.6-0"
         env:
           TUF_ROOT: /tmp
 
@@ -44,7 +44,7 @@ jobs:
     needs:
       - check-signature
     container:
-      image: ghcr.io/gythialy/golang-cross:v1.23.4-0@sha256:fac6f9675e3e3a4ccc2b8cdab87b907cabae3a9c4e046d16816b9876315005e4
+      image: ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05
 
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index 49e2ca1..3ab47fd 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -70,7 +70,7 @@ jobs:
           check-latest: true
 
       - name: golangci-lint
-        uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0
+        uses: golangci/golangci-lint-action@2e788936b09dd82dc280e845628a40d2ba6b204c # v6.3.1
         with:
           version: v1.63
 
diff --git a/.gitignore b/.gitignore
index ba4c8f0..f392acd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,4 +41,4 @@ hack/tools/bin
 
 # macOS
 .DS_Store
-fulcio-certificate-maker
+certificate-maker
diff --git a/CODEOWNERS b/CODEOWNERS
index bb1de2b..7657009 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,9 +1,6 @@
-@sigstore/fulcio-codeowners
+* @sigstore/fulcio-codeowners
 
-# The CODEOWNERS are managed via a GitHub team, but the current list is (in alphabetical order):
-
-# bobcallaway
-# dlorenc
-# haydentherapper
-# lukehinds
-# mattmoor
+/.github/ @sigstore/dep-maintainers
+/release/ @sigstore/dep-maintainers
+go.mod @sigstore/dep-maintainers
+go.sum @sigstore/dep-maintainers
diff --git a/Dockerfile b/Dockerfile
index 45c6d9b..bd40c8e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.23.5@sha256:8c10f21bec412f08f73aa7b97ca5ac5f28a39d8a88030ad8a339fd0a781d72b4 AS builder
+FROM golang:1.23.6@sha256:927112936d6b496ed95f55f362cc09da6e3e624ef868814c56d55bd7323e0959 AS builder
 ENV APP_ROOT=/opt/app-root
 ENV GOPATH=$APP_ROOT
 
@@ -28,7 +28,7 @@ RUN go build -o server main.go
 RUN CGO_ENABLED=1 go build -gcflags "all=-N -l" -o server_debug main.go
 
 # Multi-Stage production build
-FROM golang:1.23.5@sha256:8c10f21bec412f08f73aa7b97ca5ac5f28a39d8a88030ad8a339fd0a781d72b4 AS deploy
+FROM golang:1.23.6@sha256:927112936d6b496ed95f55f362cc09da6e3e624ef868814c56d55bd7323e0959 AS deploy
 
 # Retrieve the binary from the previous stage
 COPY --from=builder /opt/app-root/src/server /usr/local/bin/fulcio-server
diff --git a/Dockerfile.ctfe_init b/Dockerfile.ctfe_init
index 1faeca9..f69c362 100644
--- a/Dockerfile.ctfe_init
+++ b/Dockerfile.ctfe_init
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.23.5@sha256:8c10f21bec412f08f73aa7b97ca5ac5f28a39d8a88030ad8a339fd0a781d72b4 AS builder
+FROM golang:1.23.6@sha256:927112936d6b496ed95f55f362cc09da6e3e624ef868814c56d55bd7323e0959 AS builder
 
 WORKDIR /root/
 
diff --git a/Makefile b/Makefile
index bb08d96..987de29 100644
--- a/Makefile
+++ b/Makefile
@@ -78,7 +78,7 @@ fulcio: $(SRCS) ## Build Fulcio for local tests
 	go build -trimpath -ldflags "$(LDFLAGS)"
 
 cert-maker: ## Build the Fulcio Certificate Maker tool
-	go build -trimpath -ldflags "$(LDFLAGS)" -o fulcio-certificate-maker ./cmd/certificate_maker
+	go build -trimpath -ldflags "$(LDFLAGS)" -o certificate-maker ./cmd/certificate_maker
 
 test: ## Runs go test
 	go test ./...
@@ -86,7 +86,10 @@ test: ## Runs go test
 clean: ## Clean the workspace
 	rm -rf dist
 	rm -rf fulcio
-	rm -rf fulcio-certificate-maker
+	rm -rf certificate-maker
+	rm -rf root.pem
+	rm -rf intermediate.pem
+	rm -rf leaf.pem
 
 clean-gen: clean
 	rm -rf $(shell find pkg/generated -iname "*.go") *.swagger.json
diff --git a/README.md b/README.md
index 68c4754..dc62182 100644
--- a/README.md
+++ b/README.md
@@ -77,10 +77,13 @@ mygUY7Ii2zbdCdliiow=
 
 ### Certificate Maker
 
-The Fulcio's Certificate Maker is a tool for creating Fulcio compliant certificate chains. It supports:
+Certificate Maker is a tool for creating [Fulcio compliant certificate chains](docs/certificate-specification.md). It supports:
 
-* Two-level chains (root -> leaf)
-* Three-level chains (root -> intermediate -> leaf)
+* Two-level chains:
+  * root → leaf
+  * root → intermediate
+* Three-level chains:
+  * root → intermediate → leaf
 * Multiple KMS providers (AWS, Google Cloud, Azure, HashiCorp Vault)
 
 For detailed usage instructions and examples, see the [Certificate Maker documentation](docs/certificate-maker.md).
diff --git a/cmd/certificate_maker/certificate_maker.go b/cmd/certificate_maker/certificate_maker.go
index d30e0f6..7aa5f55 100644
--- a/cmd/certificate_maker/certificate_maker.go
+++ b/cmd/certificate_maker/certificate_maker.go
@@ -18,7 +18,6 @@
 package main
 
 import (
-	"context"
 	"fmt"
 	"os"
 	"strings"
@@ -38,7 +37,7 @@ var (
 	version string
 
 	rootCmd = &cobra.Command{
-		Use:     "fulcio-certificate-maker",
+		Use:     "certificate-maker",
 		Short:   "Create certificate chains for Fulcio",
 		Long:    `A tool for creating root, intermediate, and leaf certificates for Fulcio with code signing capabilities`,
 		Version: version,
@@ -50,7 +49,7 @@ var (
 		Long: `Create a certificate chain with the specified common name.
 The common name will be used as the Subject Common Name for the certificates.
 If no common name is provided, the values from the templates will be used.
-Example: fulcio-certificate-maker create "https://fulcio.example.com"`,
+Example: certificate-maker create "https://fulcio.example.com"`,
 		Args: cobra.RangeArgs(0, 1),
 		RunE: runCreate,
 	}
@@ -137,8 +136,6 @@ func init() {
 
 func runCreate(_ *cobra.Command, args []string) error {
 	defer func() { rootCmd.SilenceUsage = true }()
-	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-	defer cancel()
 
 	// Get common name from args if provided, otherwise templates used
 	var commonName string
@@ -148,12 +145,10 @@ func runCreate(_ *cobra.Command, args []string) error {
 
 	// Build KMS config from flags and environment
 	config := certmaker.KMSConfig{
-		CommonName:        commonName,
-		Type:              viper.GetString("kms-type"),
-		RootKeyID:         viper.GetString("root-key-id"),
-		IntermediateKeyID: viper.GetString("intermediate-key-id"),
-		LeafKeyID:         viper.GetString("leaf-key-id"),
-		Options:           make(map[string]string),
+		CommonName: commonName,
+		Type:       viper.GetString("kms-type"),
+		KeyID:      viper.GetString("root-key-id"),
+		Options:    make(map[string]string),
 	}
 
 	// Handle KMS provider options
@@ -186,11 +181,6 @@ func runCreate(_ *cobra.Command, args []string) error {
 		}
 	}
 
-	km, err := certmaker.InitKMS(ctx, config)
-	if err != nil {
-		return fmt.Errorf("failed to initialize KMS: %w", err)
-	}
-
 	// Get template paths
 	rootTemplate := viper.GetString("root-template")
 	intermediateTemplate := viper.GetString("intermediate-template")
@@ -213,7 +203,7 @@ func runCreate(_ *cobra.Command, args []string) error {
 		}
 	}
 
-	return certmaker.CreateCertificates(km, config,
+	return certmaker.CreateCertificates(config,
 		rootTemplate,
 		leafTemplate,
 		viper.GetString("root-cert"),
@@ -221,6 +211,7 @@ func runCreate(_ *cobra.Command, args []string) error {
 		viper.GetString("intermediate-key-id"),
 		viper.GetString("intermediate-template"),
 		viper.GetString("intermediate-cert"),
+		viper.GetString("leaf-key-id"),
 		viper.GetDuration("root-lifetime"),
 		viper.GetDuration("intermediate-lifetime"),
 		viper.GetDuration("leaf-lifetime"))
diff --git a/docs/certificate-maker.md b/docs/certificate-maker.md
index 96f65a1..26812be 100644
--- a/docs/certificate-maker.md
+++ b/docs/certificate-maker.md
@@ -1,11 +1,8 @@
-# Fulcio Certificate Maker
+# Certificate Maker
 
-This tool creates root, intermediate (optional), and leaf certificates for Fulcio ([certificate requirements](certificate-specification.md)):
+This tool creates certificates (root, intermediate, and leaf) that meet Fulcio's ([certificate requirements](certificate-specification.md)).
 
-- Two-level chain (root -> leaf)
-- Three-level chain (root -> intermediate -> leaf)
-
-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.
+It 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.
 
 ## Requirements
 
@@ -18,7 +15,7 @@ Build the binary:
 
 ```bash
 make cert-maker
-./fulcio-certificate-maker --help
+./certificate-maker --help
 ```
 
 ## Usage
@@ -30,7 +27,7 @@ The tool can be configured using either command-line flags or environment variab
 The `create` command accepts an optional positional argument for the common name:
 
 ```bash
-fulcio-certificate-maker create [common-name]
+./certificate-maker create [common-name]
 ```
 
 If no common name is provided, the values from the templates will be used.
@@ -38,22 +35,27 @@ If no common name is provided, the values from the templates will be used.
 Available flags:
 
 - `--kms-type`: KMS provider type (awskms, gcpkms, azurekms, hashivault)
+
 - `--root-key-id`: KMS key identifier for root certificate
+- `--intermediate-key-id`: KMS key identifier for intermediate certificate
 - `--leaf-key-id`: KMS key identifier for leaf certificate
+
 - `--aws-region`: AWS region (required for AWS KMS)
 - `--azure-tenant-id`: Azure KMS tenant ID
 - `--gcp-credentials-file`: Path to credentials file (for Google Cloud KMS)
 - `--vault-address`: HashiCorp Vault address
 - `--vault-token`: HashiCorp Vault token
+
 - `--root-template`: Path to root certificate template
-- `--leaf-template`: Path to leaf certificate template
+- `--root-lifetime`: Root certificate lifetime (default: 87600h, 10 years)
 - `--root-cert`: Output path for root certificate (default: root.pem)
-- `--leaf-cert`: Output path for leaf certificate (default: leaf.pem)
-- `--intermediate-key-id`: KMS key identifier for intermediate certificate
+
 - `--intermediate-template`: Path to intermediate certificate template
 - `--intermediate-cert`: Output path for intermediate certificate
-- `--root-lifetime`: Root certificate lifetime (default: 87600h, 10 years)
 - `--intermediate-lifetime`: Intermediate certificate lifetime (default: 43800h, 5 years)
+
+- `--leaf-template`: Path to leaf certificate template
+- `--leaf-cert`: Output path for leaf certificate (default: leaf.pem)
 - `--leaf-lifetime`: Leaf certificate lifetime (default: 8760h, 1 year)
 
 ### Environment Variables
@@ -261,7 +263,7 @@ Certificate:
 Example with AWS KMS:
 
 ```bash
-fulcio-certificate-maker create "https://fulcio.example.com" \
+./certificate-maker create "https://fulcio.example.com" \
   --kms-type awskms \
   --aws-region us-east-1 \
   --root-key-id alias/fulcio-root \
@@ -275,7 +277,7 @@ fulcio-certificate-maker create "https://fulcio.example.com" \
 Example with Azure KMS:
 
 ```bash
-fulcio-certificate-maker create "https://fulcio.example.com" \
+./certificate-maker create "https://fulcio.example.com" \
   --kms-type azurekms \
   --azure-tenant-id 1b4a4fed-fed8-4823-a8a0-3d5cea83d122 \
   --root-key-id "azurekms:name=sigstore-key;vault=sigstore-key" \
@@ -292,7 +294,7 @@ fulcio-certificate-maker create "https://fulcio.example.com" \
 Example with GCP KMS:
 
 ```bash
-fulcio-certificate-maker create "https://fulcio.example.com" \
+./certificate-maker create "https://fulcio.example.com" \
   --kms-type gcpkms \
   --gcp-credentials-file ~/.config/gcloud/application_default_credentials.json \
   --root-key-id  projects/<project_id>/locations/<location>/keyRings/<keyring>/cryptoKeys/fulcio-key1/cryptoKeyVersions/<version> \
@@ -309,7 +311,7 @@ fulcio-certificate-maker create "https://fulcio.example.com" \
 Example with HashiCorp Vault KMS:
 
 ```bash
-fulcio-certificate-maker create "https://fulcio.example.com" \
+./certificate-maker create "https://fulcio.example.com" \
   --kms-type hashivault \
   --vault-address http://vault:8200 \
   --vault-token token \
diff --git a/go.mod b/go.mod
index 38f1abf..814cd41 100644
--- a/go.mod
+++ b/go.mod
@@ -19,7 +19,7 @@ require (
 	github.com/google/go-cmp v0.6.0
 	github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99
-	github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0
 	github.com/hashicorp/golang-lru v1.0.2
 	github.com/magiconair/properties v1.8.9
 	github.com/prometheus/client_golang v1.20.5
@@ -42,7 +42,7 @@ require (
 	go.step.sm/crypto v0.57.0
 	go.uber.org/zap v1.27.0
 	google.golang.org/api v0.218.0
-	google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb
+	google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f
 	google.golang.org/grpc v1.70.0
 	google.golang.org/protobuf v1.36.3
 	gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
index bf23363..1b5e188 100644
--- a/go.sum
+++ b/go.sum
@@ -175,8 +175,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa
 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -516,8 +516,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
 google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
-google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U=
-google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
+google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
 google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
diff --git a/pkg/ca/csc.go b/pkg/ca/csc.go
index da9598b..4c63af8 100644
--- a/pkg/ca/csc.go
+++ b/pkg/ca/csc.go
@@ -68,9 +68,6 @@ func CreateCSCFromDER(cert []byte, chain []*x509.Certificate) (*CodeSigningCerti
 
 	// convert to X509 and store both formats
 	c.FinalChain = chain
-	if err != nil {
-		return nil, err
-	}
 	for _, chainCert := range c.FinalChain {
 		c.finalChainPEM = append(c.finalChainPEM, strings.TrimSpace(string(cryptoutils.PEMEncode(cryptoutils.CertificatePEMType, chainCert.Raw))))
 	}
diff --git a/pkg/certmaker/certmaker.go b/pkg/certmaker/certmaker.go
index c0f3407..8af160b 100644
--- a/pkg/certmaker/certmaker.go
+++ b/pkg/certmaker/certmaker.go
@@ -49,12 +49,10 @@ type CryptoSignerVerifier interface {
 
 // KMSConfig holds config for KMS providers.
 type KMSConfig struct {
-	CommonName        string
-	Type              string
-	RootKeyID         string
-	IntermediateKeyID string
-	LeafKeyID         string
-	Options           map[string]string
+	CommonName string
+	Type       string
+	KeyID      string
+	Options    map[string]string
 }
 
 // InitKMS initializes KMS provider based on the given config, KMSConfig.
@@ -68,7 +66,7 @@ var InitKMS = func(ctx context.Context, config KMSConfig) (signature.SignerVerif
 
 	switch config.Type {
 	case "awskms":
-		ref := fmt.Sprintf("awskms:///%s", config.RootKeyID)
+		ref := fmt.Sprintf("awskms:///%s", config.KeyID)
 		if awsRegion := config.Options["aws-region"]; awsRegion != "" {
 			os.Setenv("AWS_REGION", awsRegion)
 		}
@@ -78,7 +76,7 @@ var InitKMS = func(ctx context.Context, config KMSConfig) (signature.SignerVerif
 		}
 
 	case "gcpkms":
-		ref := fmt.Sprintf("gcpkms://%s", config.RootKeyID)
+		ref := fmt.Sprintf("gcpkms://%s", config.KeyID)
 		if gcpCredsFile := config.Options["gcp-credentials-file"]; gcpCredsFile != "" {
 			os.Setenv("GCP_CREDENTIALS_FILE", gcpCredsFile)
 		}
@@ -88,13 +86,13 @@ var InitKMS = func(ctx context.Context, config KMSConfig) (signature.SignerVerif
 		}
 
 	case "azurekms":
-		keyURI := config.RootKeyID
-		if strings.HasPrefix(config.RootKeyID, "azurekms:name=") {
-			nameStart := strings.Index(config.RootKeyID, "name=") + 5
-			vaultIndex := strings.Index(config.RootKeyID, ";vault=")
+		keyURI := config.KeyID
+		if strings.HasPrefix(config.KeyID, "azurekms:name=") {
+			nameStart := strings.Index(config.KeyID, "name=") + 5
+			vaultIndex := strings.Index(config.KeyID, ";vault=")
 			if vaultIndex != -1 {
-				keyName := strings.TrimSpace(config.RootKeyID[nameStart:vaultIndex])
-				vaultName := strings.TrimSpace(config.RootKeyID[vaultIndex+7:])
+				keyName := strings.TrimSpace(config.KeyID[nameStart:vaultIndex])
+				vaultName := strings.TrimSpace(config.KeyID[vaultIndex+7:])
 				keyURI = fmt.Sprintf("azurekms://%s.vault.azure.net/%s", vaultName, keyName)
 			}
 		}
@@ -111,7 +109,7 @@ var InitKMS = func(ctx context.Context, config KMSConfig) (signature.SignerVerif
 		}
 
 	case "hashivault":
-		keyURI := fmt.Sprintf("hashivault://%s", config.RootKeyID)
+		keyURI := fmt.Sprintf("hashivault://%s", config.KeyID)
 		if config.Options != nil {
 			if vaultToken := config.Options["vault-token"]; vaultToken != "" {
 				os.Setenv("VAULT_TOKEN", vaultToken)
@@ -141,15 +139,23 @@ var InitKMS = func(ctx context.Context, config KMSConfig) (signature.SignerVerif
 }
 
 // CreateCertificates creates certificates using the provided KMS and templates.
-// It creates 3 certificates (root -> intermediate -> leaf) if intermediateKeyID is provided,
-// otherwise creates just 2 certs (root -> leaf).
-func CreateCertificates(sv signature.SignerVerifier, config KMSConfig,
+// Root certificate is always required.
+// Intermediate and leaf certificates are optional based on provided key IDs and templates.
+func CreateCertificates(config KMSConfig,
 	rootTemplatePath, leafTemplatePath string,
 	rootCertPath, leafCertPath string,
 	intermediateKeyID, intermediateTemplatePath, intermediateCertPath string,
+	leafKeyID string,
 	rootLifetime, intermediateLifetime, leafLifetime time.Duration) error {
 
-	// Create root cert
+	// Initialize root KMS signer
+	rootConfig := config
+	sv, err := InitKMS(context.Background(), rootConfig)
+	if err != nil {
+		return fmt.Errorf("error initializing root KMS: %w", err)
+	}
+
+	// Create root cert (required)
 	rootPubKey, err := sv.PublicKey()
 	if err != nil {
 		return fmt.Errorf("error getting root public key: %w", err)
@@ -172,6 +178,9 @@ func CreateCertificates(sv signature.SignerVerifier, config KMSConfig,
 		if err != nil {
 			return fmt.Errorf("error getting default root template: %w", err)
 		}
+		if defaultTemplate == "" {
+			return fmt.Errorf("root template is required but no template was provided")
+		}
 		rootTemplate = defaultTemplate
 	} else {
 		// Read from FS if path is provided
@@ -200,10 +209,10 @@ func CreateCertificates(sv signature.SignerVerifier, config KMSConfig,
 	var signingCert *x509.Certificate
 	var signingKey crypto.Signer
 
+	// Create intermediate cert (optional)
 	if intermediateKeyID != "" {
-		// Create intermediate cert if key ID is provided
 		intermediateConfig := config
-		intermediateConfig.RootKeyID = intermediateKeyID
+		intermediateConfig.KeyID = intermediateKeyID
 		intermediateSV, err := InitKMS(context.Background(), intermediateConfig)
 		if err != nil {
 			return fmt.Errorf("error initializing intermediate KMS: %w", err)
@@ -262,58 +271,50 @@ func CreateCertificates(sv signature.SignerVerifier, config KMSConfig,
 		signingKey = rootSigner
 	}
 
-	// Create leaf cert
-	leafConfig := config
-	leafConfig.RootKeyID = config.LeafKeyID
-	leafSV, err := InitKMS(context.Background(), leafConfig)
-	if err != nil {
-		return fmt.Errorf("error initializing leaf KMS: %w", err)
-	}
-
-	leafPubKey, err := leafSV.PublicKey()
-	if err != nil {
-		return fmt.Errorf("error getting leaf public key: %w", err)
-	}
+	// Create leaf cert (optional)
+	if leafKeyID != "" {
+		leafConfig := config
+		leafConfig.KeyID = leafKeyID
+		leafSV, err := InitKMS(context.Background(), leafConfig)
+		if err != nil {
+			return fmt.Errorf("error initializing leaf KMS: %w", err)
+		}
 
-	leafCryptoSV, ok := leafSV.(CryptoSignerVerifier)
-	if !ok {
-		return fmt.Errorf("leaf signer does not implement CryptoSigner")
-	}
+		leafPubKey, err := leafSV.PublicKey()
+		if err != nil {
+			return fmt.Errorf("error getting leaf public key: %w", err)
+		}
 
-	_, _, err = leafCryptoSV.CryptoSigner(context.Background(), nil)
-	if err != nil {
-		return fmt.Errorf("error getting leaf crypto signer: %w", err)
-	}
+		var leafTemplate interface{}
+		if leafTemplatePath == "" {
+			defaultTemplate, err := GetDefaultTemplate("leaf")
+			if err != nil {
+				return fmt.Errorf("error getting default leaf template: %w", err)
+			}
+			leafTemplate = defaultTemplate
+		} else {
+			// Read from FS if path is provided
+			content, err := os.ReadFile(leafTemplatePath)
+			if err != nil {
+				return fmt.Errorf("leaf template error: template not found at %s: %w", leafTemplatePath, err)
+			}
+			leafTemplate = string(content)
+		}
 
-	var leafTemplate interface{}
-	if leafTemplatePath == "" {
-		defaultTemplate, err := GetDefaultTemplate("leaf")
+		leafNotAfter := time.Now().UTC().Add(leafLifetime)
+		leafTmpl, err := ParseTemplate(leafTemplate, signingCert, leafNotAfter, leafPubKey, config.CommonName)
 		if err != nil {
-			return fmt.Errorf("error getting default leaf template: %w", err)
+			return fmt.Errorf("error parsing leaf template: %w", err)
 		}
-		leafTemplate = defaultTemplate
-	} else {
-		// Read from FS if path is provided
-		content, err := os.ReadFile(leafTemplatePath)
+
+		leafCert, err := x509util.CreateCertificate(leafTmpl, signingCert, leafPubKey, signingKey)
 		if err != nil {
-			return fmt.Errorf("leaf template error: template not found at %s: %w", leafTemplatePath, err)
+			return fmt.Errorf("error creating leaf certificate: %w", err)
 		}
-		leafTemplate = string(content)
-	}
 
-	leafNotAfter := time.Now().UTC().Add(leafLifetime)
-	leafTmpl, err := ParseTemplate(leafTemplate, signingCert, leafNotAfter, leafPubKey, config.CommonName)
-	if err != nil {
-		return fmt.Errorf("error parsing leaf template: %w", err)
-	}
-
-	leafCert, err := x509util.CreateCertificate(leafTmpl, signingCert, leafPubKey, signingKey)
-	if err != nil {
-		return fmt.Errorf("error creating leaf certificate: %w", err)
-	}
-
-	if err := WriteCertificateToFile(leafCert, leafCertPath); err != nil {
-		return fmt.Errorf("error writing leaf certificate: %w", err)
+		if err := WriteCertificateToFile(leafCert, leafCertPath); err != nil {
+			return fmt.Errorf("error writing leaf certificate: %w", err)
+		}
 	}
 
 	return nil
@@ -339,7 +340,7 @@ func WriteCertificateToFile(cert *x509.Certificate, filename string) error {
 	}
 	defer f.Close()
 
-	// get cert type
+	// Get certificate type
 	certType := "leaf"
 	if cert.IsCA {
 		if cert.CheckSignatureFrom(cert) == nil {
@@ -359,6 +360,11 @@ func ValidateKMSConfig(config KMSConfig) error {
 		return fmt.Errorf("KMS type cannot be empty")
 	}
 
+	// Root key is always required
+	if config.KeyID == "" {
+		return fmt.Errorf("KeyID must be specified")
+	}
+
 	switch config.Type {
 	case "awskms":
 		// AWS KMS validation
@@ -387,21 +393,12 @@ func ValidateKMSConfig(config KMSConfig) error {
 			}
 			return nil
 		}
-		if err := validateAWSKeyID(config.RootKeyID, "RootKeyID"); err != nil {
-			return err
-		}
-		if err := validateAWSKeyID(config.IntermediateKeyID, "IntermediateKeyID"); err != nil {
-			return err
-		}
-		if err := validateAWSKeyID(config.LeafKeyID, "LeafKeyID"); err != nil {
+		if err := validateAWSKeyID(config.KeyID, "KeyID"); err != nil {
 			return err
 		}
 
 	case "gcpkms":
 		// GCP KMS validation
-		if config.Options == nil || config.Options["gcp-credentials-file"] == "" {
-			return fmt.Errorf("gcp-credentials-file is required for GCP KMS")
-		}
 		validateGCPKeyID := func(keyID, keyType string) error {
 			if keyID == "" {
 				return nil
@@ -423,13 +420,7 @@ func ValidateKMSConfig(config KMSConfig) error {
 			}
 			return nil
 		}
-		if err := validateGCPKeyID(config.RootKeyID, "RootKeyID"); err != nil {
-			return err
-		}
-		if err := validateGCPKeyID(config.IntermediateKeyID, "IntermediateKeyID"); err != nil {
-			return err
-		}
-		if err := validateGCPKeyID(config.LeafKeyID, "LeafKeyID"); err != nil {
+		if err := validateGCPKeyID(config.KeyID, "KeyID"); err != nil {
 			return err
 		}
 
@@ -461,13 +452,7 @@ func ValidateKMSConfig(config KMSConfig) error {
 			}
 			return nil
 		}
-		if err := validateAzureKeyID(config.RootKeyID, "RootKeyID"); err != nil {
-			return err
-		}
-		if err := validateAzureKeyID(config.IntermediateKeyID, "IntermediateKeyID"); err != nil {
-			return err
-		}
-		if err := validateAzureKeyID(config.LeafKeyID, "LeafKeyID"); err != nil {
+		if err := validateAzureKeyID(config.KeyID, "KeyID"); err != nil {
 			return err
 		}
 
@@ -498,13 +483,7 @@ func ValidateKMSConfig(config KMSConfig) error {
 			}
 			return nil
 		}
-		if err := validateHashiVaultKeyID(config.RootKeyID, "RootKeyID"); err != nil {
-			return err
-		}
-		if err := validateHashiVaultKeyID(config.IntermediateKeyID, "IntermediateKeyID"); err != nil {
-			return err
-		}
-		if err := validateHashiVaultKeyID(config.LeafKeyID, "LeafKeyID"); err != nil {
+		if err := validateHashiVaultKeyID(config.KeyID, "KeyID"); err != nil {
 			return err
 		}
 
@@ -512,13 +491,5 @@ func ValidateKMSConfig(config KMSConfig) error {
 		return fmt.Errorf("unsupported KMS type: %s", config.Type)
 	}
 
-	// Check that both key IDs are specified
-	if config.RootKeyID == "" {
-		return fmt.Errorf("RootKeyID must be specified")
-	}
-	if config.LeafKeyID == "" {
-		return fmt.Errorf("LeafKeyID must be specified")
-	}
-
 	return nil
 }
diff --git a/pkg/certmaker/template.go b/pkg/certmaker/template.go
index c1ecedc..a8322ce 100644
--- a/pkg/certmaker/template.go
+++ b/pkg/certmaker/template.go
@@ -134,12 +134,30 @@ func ValidateTemplate(filename string, _ *x509.Certificate, _ string) error {
 func GetDefaultTemplate(certType string) (string, error) {
 	switch certType {
 	case "root":
+		if rootTemplate == "" {
+			return "", fmt.Errorf("root template is required but not found")
+		}
 		return rootTemplate, nil
+	// Both intermediate and leaf are optional - return empty if not found
 	case "intermediate":
+		if intermediateTemplate == "" {
+			return "", nil
+		}
 		return intermediateTemplate, nil
 	case "leaf":
+		if leafTemplate == "" {
+			return "", nil
+		}
 		return leafTemplate, nil
 	default:
 		return "", fmt.Errorf("invalid certificate type: %s", certType)
 	}
 }
+
+// Ensures that required templates are present
+func ValidateTemplateRequirements() error {
+	if rootTemplate == "" {
+		return fmt.Errorf("root template is required but not found")
+	}
+	return nil
+}
diff --git a/release/cloudbuild.yaml b/release/cloudbuild.yaml
index b42ed2b..642eb04 100644
--- a/release/cloudbuild.yaml
+++ b/release/cloudbuild.yaml
@@ -32,19 +32,19 @@ steps:
         echo "Checking out ${_GIT_TAG}"
         git checkout ${_GIT_TAG}
 
-  - name: 'ghcr.io/sigstore/cosign/cosign:v2.4.1-dev@sha256:a1bb112f1758703aa1d222bf30b9655d04cf196c0b7feaf3479d1222c2283590'
+  - name: 'ghcr.io/sigstore/cosign/cosign:v2.4.2-dev@sha256:b69af124cb744c22eba955ebaf9514b42b1556811211e1cd744331350ccd815a'
     dir: "go/src/sigstore/fulcio"
     env:
       - TUF_ROOT=/tmp
     args:
       - 'verify'
-      - 'ghcr.io/gythialy/golang-cross:v1.23.4-0@sha256:fac6f9675e3e3a4ccc2b8cdab87b907cabae3a9c4e046d16816b9876315005e4'
+      - 'ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05'
       - '--certificate-oidc-issuer'
       - "https://token.actions.githubusercontent.com"
       - '--certificate-identity'
-      - "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.23.4-0"
+      - "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.23.6-0"
 
-  - name: ghcr.io/gythialy/golang-cross:v1.23.4-0@sha256:fac6f9675e3e3a4ccc2b8cdab87b907cabae3a9c4e046d16816b9876315005e4
+  - name: ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05
     entrypoint: /bin/sh
     dir: "go/src/sigstore/fulcio"
     env:
@@ -67,7 +67,7 @@ steps:
         gcloud auth configure-docker \
         && make release
 
-  - name: ghcr.io/gythialy/golang-cross:v1.23.4-0@sha256:fac6f9675e3e3a4ccc2b8cdab87b907cabae3a9c4e046d16816b9876315005e4
+  - name: ghcr.io/gythialy/golang-cross:v1.23.6-0@sha256:6e8da612570af388b204b833c0925c669d00f7654cf964e5bc3e808013234c05
     entrypoint: 'bash'
     dir: "go/src/sigstore/fulcio"
     env:


Test patch:
diff --git a/pkg/certmaker/certmaker_test.go b/pkg/certmaker/certmaker_test.go
index a0f587c..738dbf4 100644
--- a/pkg/certmaker/certmaker_test.go
+++ b/pkg/certmaker/certmaker_test.go
@@ -152,12 +152,11 @@ func TestCreateCertificates(t *testing.T) {
 		return mockSigner, nil
 	}
 
-	err := CreateCertificates(mockSigner, KMSConfig{
-		Type:      "awskms",
-		RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		Options:   map[string]string{"aws-region": "us-west-2"},
-	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "", "", "",
+	err := CreateCertificates(KMSConfig{
+		Type:    "awskms",
+		KeyID:   "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
+		Options: map[string]string{"aws-region": "us-west-2"},
+	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "", "", "", "",
 		87600*time.Hour, 43800*time.Hour, 8760*time.Hour)
 
 	require.Error(t, err)
@@ -177,12 +176,11 @@ func TestCreateCertificates(t *testing.T) {
 		return mockSigner, nil
 	}
 
-	err = CreateCertificates(mockSigner, KMSConfig{
-		Type:      "awskms",
-		RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		Options:   map[string]string{"aws-region": "us-west-2"},
-	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "", "", "",
+	err = CreateCertificates(KMSConfig{
+		Type:    "awskms",
+		KeyID:   "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
+		Options: map[string]string{"aws-region": "us-west-2"},
+	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "", "", "", "",
 		87600*time.Hour, 43800*time.Hour, 8760*time.Hour)
 
 	require.Error(t, err)
@@ -199,79 +197,45 @@ func TestCreateCertificates(t *testing.T) {
 		return mockSigner, nil
 	}
 
-	err = CreateCertificates(mockSigner, KMSConfig{
-		Type:      "awskms",
-		RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		Options:   map[string]string{"aws-region": "us-west-2"},
-	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "", "", "",
+	err = CreateCertificates(KMSConfig{
+		Type:    "awskms",
+		KeyID:   "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
+		Options: map[string]string{"aws-region": "us-west-2"},
+	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "", "", "", "",
 		87600*time.Hour, 43800*time.Hour, 8760*time.Hour)
 
 	require.NoError(t, err)
 
-	InitKMS = func(_ context.Context, config KMSConfig) (signature.SignerVerifier, error) {
-		if config.RootKeyID == "intermediate-key" {
-			return nil, fmt.Errorf("intermediate KMS error")
-		}
-		return mockSigner, nil
-	}
-
-	err = CreateCertificates(mockSigner, KMSConfig{
-		Type:      "awskms",
-		RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		Options:   map[string]string{"aws-region": "us-west-2"},
-	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "intermediate-key", intermediateTmplPath, intermediateCertPath,
-		87600*time.Hour, 43800*time.Hour, 8760*time.Hour)
-
-	require.Error(t, err)
-	assert.Contains(t, err.Error(), "error initializing intermediate KMS: intermediate KMS error")
-
 	intermediateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	require.NoError(t, err)
 
+	leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	require.NoError(t, err)
+
 	mockIntermediateSigner := &mockSignerVerifier{
 		key: intermediateKey,
-		publicKeyFunc: func() (crypto.PublicKey, error) {
-			return nil, fmt.Errorf("intermediate public key error")
-		},
 	}
 
-	InitKMS = func(_ context.Context, config KMSConfig) (signature.SignerVerifier, error) {
-		if config.RootKeyID == "intermediate-key" {
-			return mockIntermediateSigner, nil
-		}
-		return mockSigner, nil
-	}
-
-	err = CreateCertificates(mockSigner, KMSConfig{
-		Type:      "awskms",
-		RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		Options:   map[string]string{"aws-region": "us-west-2"},
-	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "intermediate-key", intermediateTmplPath, intermediateCertPath,
-		87600*time.Hour, 43800*time.Hour, 8760*time.Hour)
-
-	require.Error(t, err)
-	assert.Contains(t, err.Error(), "error getting intermediate public key: intermediate public key error")
-
-	mockIntermediateSigner = &mockSignerVerifier{
-		key: intermediateKey,
+	mockLeafSigner := &mockSignerVerifier{
+		key: leafKey,
 	}
 
 	InitKMS = func(_ context.Context, config KMSConfig) (signature.SignerVerifier, error) {
-		if config.RootKeyID == "intermediate-key" {
+		switch config.KeyID {
+		case "intermediate-key":
 			return mockIntermediateSigner, nil
+		case "leaf-key":
+			return mockLeafSigner, nil
+		default:
+			return mockSigner, nil
 		}
-		return mockSigner, nil
 	}
 
-	err = CreateCertificates(mockSigner, KMSConfig{
-		Type:      "awskms",
-		RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-		Options:   map[string]string{"aws-region": "us-west-2"},
-	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "intermediate-key", intermediateTmplPath, intermediateCertPath,
+	err = CreateCertificates(KMSConfig{
+		Type:    "awskms",
+		KeyID:   "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
+		Options: map[string]string{"aws-region": "us-west-2"},
+	}, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath, "intermediate-key", intermediateTmplPath, intermediateCertPath, "leaf-key",
 		87600*time.Hour, 43800*time.Hour, 8760*time.Hour)
 
 	require.NoError(t, err)
@@ -293,36 +257,32 @@ func TestValidateKMSConfig(t *testing.T) {
 		{
 			name: "valid AWS config",
 			config: KMSConfig{
-				Type:      "awskms",
-				RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				Options:   map[string]string{"aws-region": "us-west-2"},
+				Type:    "awskms",
+				KeyID:   "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
+				Options: map[string]string{"aws-region": "us-west-2"},
 			},
 		},
 		{
 			name: "valid GCP config",
 			config: KMSConfig{
-				Type:      "gcpkms",
-				RootKeyID: "projects/my-project/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
-				LeafKeyID: "projects/my-project/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
-				Options:   map[string]string{"gcp-credentials-file": "/path/to/creds.json"},
+				Type:    "gcpkms",
+				KeyID:   "projects/my-project/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
+				Options: map[string]string{"gcp-credentials-file": "/path/to/creds.json"},
 			},
 		},
 		{
 			name: "valid Azure config",
 			config: KMSConfig{
-				Type:      "azurekms",
-				RootKeyID: "azurekms:name=key1;vault=vault1",
-				LeafKeyID: "azurekms:name=key2;vault=vault1",
-				Options:   map[string]string{"azure-tenant-id": "tenant-id"},
+				Type:    "azurekms",
+				KeyID:   "azurekms:name=key1;vault=vault1",
+				Options: map[string]string{"azure-tenant-id": "tenant-id"},
 			},
 		},
 		{
 			name: "valid HashiVault config",
 			config: KMSConfig{
-				Type:      "hashivault",
-				RootKeyID: "transit/keys/root-key",
-				LeafKeyID: "transit/keys/leaf-key",
+				Type:  "hashivault",
+				KeyID: "transit/keys/root-key",
 				Options: map[string]string{
 					"vault-token":   "token",
 					"vault-address": "http://localhost:8200",
@@ -332,27 +292,24 @@ func TestValidateKMSConfig(t *testing.T) {
 		{
 			name: "missing AWS region",
 			config: KMSConfig{
-				Type:      "awskms",
-				RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
+				Type:  "awskms",
+				KeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
 			},
 			wantError: "aws-region is required for AWS KMS",
 		},
 		{
 			name: "missing Azure tenant ID",
 			config: KMSConfig{
-				Type:      "azurekms",
-				RootKeyID: "azurekms:name=key1;vault=vault1",
-				LeafKeyID: "azurekms:name=key2;vault=vault1",
+				Type:  "azurekms",
+				KeyID: "azurekms:name=key1;vault=vault1",
 			},
 			wantError: "options map is required for Azure KMS",
 		},
 		{
 			name: "missing HashiVault token",
 			config: KMSConfig{
-				Type:      "hashivault",
-				RootKeyID: "transit/keys/root-key",
-				LeafKeyID: "transit/keys/leaf-key",
+				Type:  "hashivault",
+				KeyID: "transit/keys/root-key",
 				Options: map[string]string{
 					"vault-address": "http://localhost:8200",
 				},
@@ -362,9 +319,8 @@ func TestValidateKMSConfig(t *testing.T) {
 		{
 			name: "missing HashiVault address",
 			config: KMSConfig{
-				Type:      "hashivault",
-				RootKeyID: "transit/keys/root-key",
-				LeafKeyID: "transit/keys/leaf-key",
+				Type:  "hashivault",
+				KeyID: "transit/keys/root-key",
 				Options: map[string]string{
 					"vault-token": "token",
 				},
@@ -374,82 +330,66 @@ func TestValidateKMSConfig(t *testing.T) {
 		{
 			name: "unsupported KMS type",
 			config: KMSConfig{
-				Type:      "unsupported",
-				RootKeyID: "key1",
-				LeafKeyID: "key2",
+				Type:  "unsupported",
+				KeyID: "key1",
 			},
 			wantError: "unsupported KMS type: unsupported",
 		},
 		{
 			name: "invalid AWS key ID format",
 			config: KMSConfig{
-				Type:      "awskms",
-				RootKeyID: "invalid-key-id",
-				LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				Options:   map[string]string{"aws-region": "us-west-2"},
+				Type:    "awskms",
+				KeyID:   "invalid-key-id",
+				Options: map[string]string{"aws-region": "us-west-2"},
 			},
-			wantError: "awskms RootKeyID must start with 'arn:aws:kms:' or 'alias/'",
+			wantError: "awskms KeyID must start with 'arn:aws:kms:' or 'alias/'",
 		},
 		{
 			name: "AWS key ID region mismatch",
 			config: KMSConfig{
-				Type:      "awskms",
-				RootKeyID: "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
-				LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				Options:   map[string]string{"aws-region": "us-west-2"},
+				Type:    "awskms",
+				KeyID:   "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
+				Options: map[string]string{"aws-region": "us-west-2"},
 			},
 			wantError: "region in ARN (us-east-1) does not match configured region (us-west-2)",
 		},
 		{
 			name: "invalid GCP key ID format",
 			config: KMSConfig{
-				Type:      "gcpkms",
-				RootKeyID: "invalid/key/path",
-				LeafKeyID: "projects/my-project/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
-				Options:   map[string]string{"gcp-credentials-file": "/path/to/creds.json"},
+				Type:    "gcpkms",
+				KeyID:   "invalid/key/path",
+				Options: map[string]string{"gcp-credentials-file": "/path/to/creds.json"},
 			},
-			wantError: "gcpkms RootKeyID must start with 'projects/'",
+			wantError: "gcpkms KeyID must start with 'projects/'",
 		},
 		{
 			name: "invalid Azure key ID format",
 			config: KMSConfig{
-				Type:      "azurekms",
-				RootKeyID: "invalid-key-id",
-				LeafKeyID: "azurekms:name=key2;vault=vault1",
-				Options:   map[string]string{"azure-tenant-id": "tenant-id"},
+				Type:    "azurekms",
+				KeyID:   "invalid-key-id",
+				Options: map[string]string{"azure-tenant-id": "tenant-id"},
 			},
-			wantError: "azurekms RootKeyID must start with 'azurekms:name='",
+			wantError: "azurekms KeyID must start with 'azurekms:name='",
 		},
 		{
 			name: "invalid HashiVault key ID format",
 			config: KMSConfig{
-				Type:      "hashivault",
-				RootKeyID: "invalid/path",
-				LeafKeyID: "transit/keys/leaf-key",
+				Type:  "hashivault",
+				KeyID: "invalid/path",
 				Options: map[string]string{
 					"vault-token":   "token",
 					"vault-address": "http://localhost:8200",
 				},
 			},
-			wantError: "hashivault RootKeyID must be in format: transit/keys/keyname",
-		},
-		{
-			name: "missing root key ID",
-			config: KMSConfig{
-				Type:      "awskms",
-				LeafKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				Options:   map[string]string{"aws-region": "us-west-2"},
-			},
-			wantError: "RootKeyID must be specified",
+			wantError: "hashivault KeyID must be in format: transit/keys/keyname",
 		},
 		{
-			name: "missing leaf key ID",
+			name: "missing key ID",
 			config: KMSConfig{
-				Type:      "awskms",
-				RootKeyID: "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012",
-				Options:   map[string]string{"aws-region": "us-west-2"},
+				Type:    "awskms",
+				Options: map[string]string{"aws-region": "us-west-2"},
 			},
-			wantError: "LeafKeyID must be specified",
+			wantError: "KeyID must be specified",
 		},
 	}