Skip to content

Commit

Permalink
fix: validates code signing and includes leaf wording.
Browse files Browse the repository at this point in the history
Signed-off-by: ianhundere <[email protected]>
  • Loading branch information
ianhundere committed Nov 28, 2024
1 parent f5c3975 commit 06cb522
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 129 deletions.
52 changes: 27 additions & 25 deletions cmd/certificate_maker/certificate_maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//

// Package main implements a certificate creation utility for Fulcio.
// It supports creating root and intermediate certificates using(AWS, GCP, Azure).
// It supports creating root and leaf certificates using (AWS, GCP, Azure).
package main

import (
Expand All @@ -28,14 +28,16 @@ import (
"go.uber.org/zap"
)

// CLI flags and env vars for config.
// Supports AWS KMS, Google Cloud KMS, and Azure Key Vault configurations.
var (
logger *zap.Logger
version string

rootCmd = &cobra.Command{
Use: "fulcio-certificate-maker",
Short: "Create certificate chains for Fulcio CA",
Long: `A tool for creating root and intermediate certificates for Fulcio CA with code signing capabilities`,
Long: `A tool for creating root and leaf certificates for Fulcio CA with code signing capabilities`,
Version: version,
}

Expand All @@ -45,18 +47,18 @@ var (
RunE: runCreate,
}

kmsType string
kmsRegion string
kmsKeyID string
kmsVaultName string
kmsTenantID string
kmsCredsFile string
rootTemplatePath string
intermTemplatePath string
rootKeyID string
intermediateKeyID string
rootCertPath string
intermCertPath string
kmsType string
kmsRegion string
kmsKeyID string
kmsVaultName string
kmsTenantID string
kmsCredsFile string
rootTemplatePath string
leafTemplatePath string
rootKeyID string
leafKeyID string
rootCertPath string
leafCertPath string

rawJSON = []byte(`{
"level": "debug",
Expand Down Expand Up @@ -86,21 +88,21 @@ func init() {
createCmd.Flags().StringVar(&kmsTenantID, "kms-tenant-id", "", "Azure KMS tenant ID")
createCmd.Flags().StringVar(&kmsCredsFile, "kms-credentials-file", "", "Path to credentials file (for Google Cloud KMS)")
createCmd.Flags().StringVar(&rootTemplatePath, "root-template", "pkg/certmaker/templates/root-template.json", "Path to root certificate template")
createCmd.Flags().StringVar(&intermTemplatePath, "intermediate-template", "pkg/certmaker/templates/intermediate-template.json", "Path to intermediate certificate template")
createCmd.Flags().StringVar(&leafTemplatePath, "leaf-template", "pkg/certmaker/templates/leaf-template.json", "Path to leaf certificate template")
createCmd.Flags().StringVar(&rootKeyID, "root-key-id", "", "KMS key identifier for root certificate")
createCmd.Flags().StringVar(&intermediateKeyID, "intermediate-key-id", "", "KMS key identifier for intermediate certificate")
createCmd.Flags().StringVar(&leafKeyID, "leaf-key-id", "", "KMS key identifier for leaf certificate")
createCmd.Flags().StringVar(&rootCertPath, "root-cert", "root.pem", "Output path for root certificate")
createCmd.Flags().StringVar(&intermCertPath, "intermediate-cert", "intermediate.pem", "Output path for intermediate certificate")
createCmd.Flags().StringVar(&leafCertPath, "leaf-cert", "leaf.pem", "Output path for leaf certificate")
}

func runCreate(cmd *cobra.Command, args []string) error {
// Build KMS config from flags and environment
config := certmaker.KMSConfig{
Type: getConfigValue(kmsType, "KMS_TYPE"),
Region: getConfigValue(kmsRegion, "KMS_REGION"),
RootKeyID: getConfigValue(rootKeyID, "KMS_ROOT_KEY_ID"),
IntermediateKeyID: getConfigValue(intermediateKeyID, "KMS_INTERMEDIATE_KEY_ID"),
Options: make(map[string]string),
Type: getConfigValue(kmsType, "KMS_TYPE"),
Region: getConfigValue(kmsRegion, "KMS_REGION"),
RootKeyID: getConfigValue(rootKeyID, "KMS_ROOT_KEY_ID"),
LeafKeyID: getConfigValue(leafKeyID, "KMS_LEAF_KEY_ID"),
Options: make(map[string]string),
}

// Handle KMS provider options
Expand Down Expand Up @@ -128,11 +130,11 @@ func runCreate(cmd *cobra.Command, args []string) error {
if err := certmaker.ValidateTemplatePath(rootTemplatePath); err != nil {
return fmt.Errorf("root template error: %w", err)
}
if err := certmaker.ValidateTemplatePath(intermTemplatePath); err != nil {
return fmt.Errorf("intermediate template error: %w", err)
if err := certmaker.ValidateTemplatePath(leafTemplatePath); err != nil {
return fmt.Errorf("leaf template error: %w", err)
}

return certmaker.CreateCertificates(km, config, rootTemplatePath, intermTemplatePath, rootCertPath, intermCertPath)
return certmaker.CreateCertificates(km, config, rootTemplatePath, leafTemplatePath, rootCertPath, leafCertPath)
}

func main() {
Expand Down
92 changes: 44 additions & 48 deletions pkg/certmaker/certmaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//

// Package certmaker implements a certificate creation utility for Fulcio.
// It supports creating root and intermediate certificates using (AWS, GCP, Azure).
// It supports creating root and leaf certificates using (AWS, GCP, Azure).
package certmaker

import (
Expand All @@ -33,28 +33,30 @@ import (
"go.step.sm/crypto/x509util"
)

// KMSConfig holds config for KMS providers.
type KMSConfig struct {
Type string // KMS provider type: "awskms", "cloudkms", "azurekms"
Region string // AWS region or Cloud location
RootKeyID string // Root CA key identifier
IntermediateKeyID string // Intermediate CA key identifier
Options map[string]string // Provider-specific options
Type string // KMS provider type: "awskms", "cloudkms", "azurekms"
Region string // AWS region or Cloud location
RootKeyID string // Root CA key identifier
LeafKeyID string // Leaf CA key identifier
Options map[string]string // Provider-specific options
}

// InitKMS initializes KMS provider based on the given config, KMSConfig.
// Supports AWS KMS, Google Cloud KMS, and Azure Key Vault.
func InitKMS(ctx context.Context, config KMSConfig) (apiv1.KeyManager, error) {
if err := ValidateKMSConfig(config); err != nil {
return nil, fmt.Errorf("invalid KMS configuration: %w", err)
}

opts := apiv1.Options{
Type: apiv1.Type(config.Type),
URI: "",
}

// Use RootKeyID as the primary key ID, fall back to IntermediateKeyID if root is not set
// Falls back to LeafKeyID if root is not set
keyID := config.RootKeyID
if keyID == "" {
keyID = config.IntermediateKeyID
keyID = config.LeafKeyID
}

switch config.Type {
Expand All @@ -80,74 +82,70 @@ func InitKMS(ctx context.Context, config KMSConfig) (apiv1.KeyManager, error) {
}

// CreateCertificates generates a certificate chain using the configured KMS provider.
// It creates both root and intermediate certificates using the provided templates
// It creates both root and leaf certificates using the provided templates
// and KMS signing keys.
func CreateCertificates(km apiv1.KeyManager, config KMSConfig, rootTemplatePath, intermediateTemplatePath, rootCertPath, intermCertPath string) error {
// Parse templates
func CreateCertificates(km apiv1.KeyManager, config KMSConfig, rootTemplatePath, leafTemplatePath, rootCertPath, leafCertPath string) error {
// Parse root template
rootTmpl, err := ParseTemplate(rootTemplatePath, nil)
if err != nil {
return fmt.Errorf("error parsing root template: %w", err)
}

rootKeyName := config.RootKeyID
if config.Type == "azurekms" {
rootKeyName = fmt.Sprintf("azurekms:vault=%s;name=%s",
config.Options["vault-name"], config.RootKeyID)
}

rootSigner, err := km.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: rootKeyName,
})
if err != nil {
return fmt.Errorf("error creating root signer: %w", err)
}

// Create root certificate
// Create root cert
rootCert, err := x509util.CreateCertificate(rootTmpl, rootTmpl, rootSigner.Public(), rootSigner)
if err != nil {
return fmt.Errorf("error creating root certificate: %w", err)
}
if err := WriteCertificateToFile(rootCert, rootCertPath); err != nil {
return fmt.Errorf("error writing root certificate: %w", err)
}

// Parse intermediate template
intermediateTmpl, err := ParseTemplate(intermediateTemplatePath, rootCert)
// Create leaf cert
leafTmpl, err := ParseTemplate(leafTemplatePath, rootCert)
if err != nil {
return fmt.Errorf("error parsing intermediate template: %w", err)
return fmt.Errorf("error parsing leaf template: %w", err)
}

intermediateKeyName := config.IntermediateKeyID
leafKeyName := config.LeafKeyID
if config.Type == "azurekms" {
intermediateKeyName = fmt.Sprintf("azurekms:vault=%s;name=%s",
config.Options["vault-name"], config.IntermediateKeyID)
leafKeyName = fmt.Sprintf("azurekms:vault=%s;name=%s",
config.Options["vault-name"], config.LeafKeyID)
}

intermediateSigner, err := km.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: intermediateKeyName,
leafSigner, err := km.CreateSigner(&apiv1.CreateSignerRequest{
SigningKey: leafKeyName,
})
if err != nil {
return fmt.Errorf("error creating intermediate signer: %w", err)
return fmt.Errorf("error creating leaf signer: %w", err)
}

// Create intermediate certificate
intermediateCert, err := x509util.CreateCertificate(intermediateTmpl, rootCert, intermediateSigner.Public(), rootSigner)
leafCert, err := x509util.CreateCertificate(leafTmpl, rootCert, leafSigner.Public(), rootSigner)
if err != nil {
return fmt.Errorf("error creating intermediate certificate: %w", err)
return fmt.Errorf("error creating leaf certificate: %w", err)
}

if err := WriteCertificateToFile(rootCert, rootCertPath); err != nil {
return fmt.Errorf("error writing root certificate: %w", err)
}

if err := WriteCertificateToFile(intermediateCert, intermCertPath); err != nil {
return fmt.Errorf("error writing intermediate certificate: %w", err)
if err := WriteCertificateToFile(leafCert, leafCertPath); err != nil {
return fmt.Errorf("error writing leaf certificate: %w", err)
}

// Verify certificate chain
// Verify cert chain
pool := x509.NewCertPool()
pool.AddCert(rootCert)
if _, err := intermediateCert.Verify(x509.VerifyOptions{
Roots: pool,
}); err != nil {
return fmt.Errorf("CA.Intermediate.Verify() error = %v", err)
opts := x509.VerifyOptions{
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
}
if _, err := leafCert.Verify(opts); err != nil {
return fmt.Errorf("certificate chain verification failed: %w", err)
}

return nil
Expand All @@ -165,11 +163,12 @@ func WriteCertificateToFile(cert *x509.Certificate, filename string) error {
return fmt.Errorf("failed to create file %s: %w", filename, err)
}
defer file.Close()

if err := pem.Encode(file, certPEM); err != nil {
return fmt.Errorf("failed to write certificate to file %s: %w", filename, err)
}

certType := "root"
fmt.Printf("Your %s certificate has been saved in %s.\n", certType, filename)
return nil
}

Expand All @@ -178,8 +177,8 @@ func ValidateKMSConfig(config KMSConfig) error {
if config.Type == "" {
return fmt.Errorf("KMS type cannot be empty")
}
if config.RootKeyID == "" && config.IntermediateKeyID == "" {
return fmt.Errorf("at least one of RootKeyID or IntermediateKeyID must be specified")
if config.RootKeyID == "" && config.LeafKeyID == "" {
return fmt.Errorf("at least one of RootKeyID or LeafKeyID must be specified")
}

switch config.Type {
Expand All @@ -191,8 +190,8 @@ func ValidateKMSConfig(config KMSConfig) error {
if config.RootKeyID != "" && !strings.HasPrefix(config.RootKeyID, "projects/") {
return fmt.Errorf("cloudkms RootKeyID must start with 'projects/'")
}
if config.IntermediateKeyID != "" && !strings.HasPrefix(config.IntermediateKeyID, "projects/") {
return fmt.Errorf("cloudkms IntermediateKeyID must start with 'projects/'")
if config.LeafKeyID != "" && !strings.HasPrefix(config.LeafKeyID, "projects/") {
return fmt.Errorf("cloudkms LeafKeyID must start with 'projects/'")
}
case "azurekms":
if config.Options["vault-name"] == "" {
Expand All @@ -211,16 +210,13 @@ func ValidateTemplatePath(path string) error {
if _, err := os.Stat(path); err != nil {
return fmt.Errorf("template not found at %s: %w", path, err)
}

if !strings.HasSuffix(path, ".json") {
return fmt.Errorf("template file must have .json extension: %s", path)
}

content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("error reading template file: %w", err)
}

var js json.RawMessage
if err := json.Unmarshal(content, &js); err != nil {
return fmt.Errorf("invalid JSON in template file: %w", err)
Expand Down
Loading

0 comments on commit 06cb522

Please sign in to comment.