diff --git a/cmd/certificate_maker/certificate_maker.go b/cmd/certificate_maker/certificate_maker.go index 17cb1caf6..726bf4858 100644 --- a/cmd/certificate_maker/certificate_maker.go +++ b/cmd/certificate_maker/certificate_maker.go @@ -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 ( @@ -28,6 +28,8 @@ 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 @@ -35,7 +37,7 @@ var ( 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, } @@ -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", @@ -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 @@ -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() { diff --git a/pkg/certmaker/certmaker.go b/pkg/certmaker/certmaker.go index 94f29bfc2..a066e9ed5 100644 --- a/pkg/certmaker/certmaker.go +++ b/pkg/certmaker/certmaker.go @@ -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 ( @@ -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 { @@ -80,21 +82,19 @@ 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, }) @@ -102,52 +102,50 @@ func CreateCertificates(km apiv1.KeyManager, config KMSConfig, rootTemplatePath, 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 @@ -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 } @@ -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 { @@ -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"] == "" { @@ -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) diff --git a/pkg/certmaker/certmaker_test.go b/pkg/certmaker/certmaker_test.go index 668682e26..770855cab 100644 --- a/pkg/certmaker/certmaker_test.go +++ b/pkg/certmaker/certmaker_test.go @@ -51,13 +51,13 @@ func newMockKMS() *mockKMS { if err != nil { panic(fmt.Errorf("failed to generate root key: %v", err)) } - intermediateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - panic(fmt.Errorf("failed to generate intermediate key: %v", err)) + panic(fmt.Errorf("failed to generate leaf key: %v", err)) } m.keys["root-key"] = rootKey - m.keys["intermediate-key"] = intermediateKey + m.keys["leaf-key"] = leafKey return m } @@ -131,7 +131,7 @@ func TestCreateCertificates(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { os.RemoveAll(tmpDir) }) - // Root template (same for both) + // root template (same for both) rootContent := `{ "subject": { "commonName": "https://blah.com" @@ -154,8 +154,8 @@ func TestCreateCertificates(t *testing.T) { "notAfter": "2025-01-01T00:00:00Z" }` - // Fulcio intermediate template - intermediateContent := `{ + // leaf template + leafContent := `{ "subject": { "commonName": "https://blah.com" }, @@ -177,7 +177,7 @@ func TestCreateCertificates(t *testing.T) { "notAfter": "2025-01-01T00:00:00Z" }` - testCertificateCreation(t, tmpDir, rootContent, intermediateContent) + testCertificateCreation(t, tmpDir, rootContent, leafContent) } // TestWriteCertificateToFile tests PEM file writing @@ -218,27 +218,27 @@ func TestWriteCertificateToFile(t *testing.T) { } // testCertificateCreation creates and verifies certificate chains -func testCertificateCreation(t *testing.T, tmpDir, rootContent, intermediateContent string) { +func testCertificateCreation(t *testing.T, tmpDir, rootContent, leafContent string) { rootTmplPath := filepath.Join(tmpDir, "root-template.json") - intermediateTmplPath := filepath.Join(tmpDir, "intermediate-template.json") + leafTmplPath := filepath.Join(tmpDir, "leaf-template.json") rootCertPath := filepath.Join(tmpDir, "root.pem") - intermediateCertPath := filepath.Join(tmpDir, "intermediate.pem") + leafCertPath := filepath.Join(tmpDir, "leaf.pem") err := os.WriteFile(rootTmplPath, []byte(rootContent), 0600) require.NoError(t, err) - err = os.WriteFile(intermediateTmplPath, []byte(intermediateContent), 0600) + err = os.WriteFile(leafTmplPath, []byte(leafContent), 0600) require.NoError(t, err) km := newMockKMS() config := KMSConfig{ - Type: "mockkms", - RootKeyID: "root-key", - IntermediateKeyID: "intermediate-key", - Options: make(map[string]string), + Type: "mockkms", + RootKeyID: "root-key", + LeafKeyID: "leaf-key", + Options: make(map[string]string), } - err = CreateCertificates(km, config, rootTmplPath, intermediateTmplPath, rootCertPath, intermediateCertPath) + err = CreateCertificates(km, config, rootTmplPath, leafTmplPath, rootCertPath, leafCertPath) require.NoError(t, err) } @@ -251,9 +251,9 @@ func TestValidateKMSConfig(t *testing.T) { { name: "valid azure config", config: KMSConfig{ - Type: "azurekms", - RootKeyID: "root-key", - IntermediateKeyID: "intermediate-key", + Type: "azurekms", + RootKeyID: "root-key", + LeafKeyID: "leaf-key", Options: map[string]string{ "vault-name": "test-vault", "tenant-id": "test-tenant", diff --git a/pkg/certmaker/template.go b/pkg/certmaker/template.go index 080ad9a35..2e7c4d900 100644 --- a/pkg/certmaker/template.go +++ b/pkg/certmaker/template.go @@ -1,6 +1,6 @@ // Package certmaker provides template parsing and certificate generation functionality // for creating Fulcio X.509 certificates from JSON templates. It supports both root and -// intermediate certificate creation with configurable properties including key usage, +// leaf certificate creation with configurable properties including key usage, // extended key usage, and basic constraints. package certmaker @@ -15,7 +15,7 @@ import ( ) // CertificateTemplate defines the JSON structure for Fulcio certificate templates. -// It supports both root and intermediate CA certificates with code signing capabilities. +// It supports both root and leaf CA certificates with code signing capabilities. type CertificateTemplate struct { Subject struct { Country []string `json:"country,omitempty"` @@ -29,7 +29,6 @@ type CertificateTemplate struct { NotBefore string `json:"notBefore"` NotAfter string `json:"notAfter"` KeyUsage []string `json:"keyUsage"` - ExtKeyUsage []string `json:"extKeyUsage,omitempty"` BasicConstraints struct { IsCA bool `json:"isCA"` MaxPathLen int `json:"maxPathLen"` @@ -39,6 +38,7 @@ type CertificateTemplate struct { Critical bool `json:"critical"` Value string `json:"value"` } `json:"extensions,omitempty"` + ExtKeyUsage []string `json:"extKeyUsage,omitempty"` } // ParseTemplate creates an x509 certificate from JSON template @@ -89,7 +89,7 @@ func ValidateTemplate(tmpl *CertificateTemplate, parent *x509.Certificate) error // Fulcio-specific validation for code signing hasCodeSigning := false for _, usage := range tmpl.ExtKeyUsage { - if usage == "codeSign" { + if usage == "CodeSigning" { hasCodeSigning = true break } @@ -159,7 +159,7 @@ func SetKeyUsages(cert *x509.Certificate, usages []string) { func SetExtKeyUsages(cert *x509.Certificate, usages []string) { for _, usage := range usages { switch usage { - case "codeSign": + case "CodeSigning": cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } } diff --git a/pkg/certmaker/templates/intermediate-template.json b/pkg/certmaker/templates/intermediate-template.json deleted file mode 100644 index ad799a530..000000000 --- a/pkg/certmaker/templates/intermediate-template.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "subject": { - "country": [ - "US" - ], - "organization": [ - "Sigstore" - ], - "organizationalUnit": [ - "Fulcio Intermediate CA" - ], - "commonName": "https://fulcio.com" - }, - "issuer": { - "commonName": "https://fulcio.com" - }, - "notBefore": "2024-01-01T00:00:00Z", - "notAfter": "2034-01-01T00:00:00Z", - "serialNumber": 2, - "basicConstraints": { - "isCA": true, - "maxPathLen": 0 - }, - "keyUsage": [ - "certSign", - "crlSign", - "digitalSignature" - ], - "extKeyUsage": [ - "CodeSigning" - ] -} \ No newline at end of file