Skip to content

Commit

Permalink
fix: enables timestamping / improves validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
ianhundere committed Nov 26, 2024
1 parent 1c6b9c6 commit 38d5a1d
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 130 deletions.
7 changes: 6 additions & 1 deletion cmd/certificate_maker/certificate_maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ import (
"encoding/json"
"fmt"
"os"
"time"

"github.com/sigstore/timestamp-authority/pkg/certmaker"
"github.com/spf13/cobra"
"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
Expand Down Expand Up @@ -96,6 +99,9 @@ func init() {
}

func runCreate(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Build KMS config from flags and environment
config := certmaker.KMSConfig{
Type: getConfigValue(kmsType, "KMS_TYPE"),
Expand All @@ -120,7 +126,6 @@ func runCreate(cmd *cobra.Command, args []string) error {
}
}

ctx := context.Background()
km, err := certmaker.InitKMS(ctx, config)
if err != nil {
return fmt.Errorf("failed to initialize KMS: %w", err)
Expand Down
53 changes: 22 additions & 31 deletions pkg/certmaker/certmaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package certmaker
import (
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"os"
Expand All @@ -33,6 +32,7 @@ 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
Expand All @@ -41,17 +41,18 @@ type KMSConfig struct {
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 IntermediateKeyID if root is not set
keyID := config.RootKeyID
if keyID == "" {
keyID = config.IntermediateKeyID
Expand Down Expand Up @@ -83,12 +84,11 @@ func InitKMS(ctx context.Context, config KMSConfig) (apiv1.KeyManager, error) {
// It creates both root and intermediate certificates using the provided templates
// and KMS signing keys.
func CreateCertificates(km apiv1.KeyManager, config KMSConfig, rootTemplatePath, intermediateTemplatePath, rootCertPath, intermCertPath string) error {
// Parse templates
// 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",
Expand All @@ -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
// Parse / sign intermediate template
intermediateTmpl, err := ParseTemplate(intermediateTemplatePath, rootCert)
if err != nil {
return fmt.Errorf("error parsing intermediate template: %w", err)
}

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

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

// Create intermediate certificate
// Create intermediate/leaf cert
intermediateCert, err := x509util.CreateCertificate(intermediateTmpl, rootCert, intermediateSigner.Public(), rootSigner)
if err != nil {
return fmt.Errorf("error creating intermediate 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)
}

// Verify certificate 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.ExtKeyUsageTimeStamping},
}
if _, err := intermediateCert.Verify(opts); err != nil {
return fmt.Errorf("certificate chain verification failed: %w", err)
}

return nil
Expand All @@ -165,11 +163,15 @@ 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"
if cert.Subject.OrganizationalUnit != nil && cert.Subject.OrganizationalUnit[0] == "TSA Intermediate CA" {
certType = "intermediate"
}
fmt.Printf("Your %s certificate has been saved in %s.\n", certType, filename)
return nil
}

Expand Down Expand Up @@ -211,20 +213,9 @@ 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)
}

return nil
}
104 changes: 28 additions & 76 deletions pkg/certmaker/certmaker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,112 +124,64 @@ func TestParseTemplate(t *testing.T) {

// TestCreateCertificates tests certificate chain creation
func TestCreateCertificates(t *testing.T) {
t.Run("Fulcio", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "cert-test-fulcio-*")
t.Run("TSA", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "cert-test-tsa-*")
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"
"country": ["US"],
"organization": ["Sigstore"],
"organizationalUnit": ["Timestamp Authority Root CA"],
"commonName": "https://tsa.com"
},
"issuer": {
"commonName": "https://blah.com"
"commonName": "https://tsa.com"
},
"keyUsage": [
"certSign",
"crlSign"
],
"extKeyUsage": [
"CodeSigning"
],
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2034-01-01T00:00:00Z",
"basicConstraints": {
"isCA": true,
"maxPathLen": 0
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z"
}`

// Fulcio intermediate template
intermediateContent := `{
"subject": {
"commonName": "https://blah.com"
},
"issuer": {
"commonName": "https://blah.com"
"maxPathLen": 1
},
"keyUsage": [
"certSign",
"crlSign"
],
"extKeyUsage": [
"CodeSigning"
],
"basicConstraints": {
"isCA": true,
"maxPathLen": 0
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z"
]
}`

testCertificateCreation(t, tmpDir, rootContent, intermediateContent)
})

t.Run("TSA", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "cert-test-tsa-*")
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(tmpDir) })

// Root template (same for both)
rootContent := `{
// intermediate template
intermediateContent := `{
"subject": {
"commonName": "https://blah.com"
"country": ["US"],
"organization": ["Sigstore"],
"organizationalUnit": ["Timestamp Authority"],
"commonName": "https://tsa.com"
},
"issuer": {
"commonName": "https://blah.com"
"commonName": "https://tsa.com"
},
"keyUsage": [
"certSign",
"crlSign"
],
"extKeyUsage": [
"CodeSigning"
],
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2034-01-01T00:00:00Z",
"basicConstraints": {
"isCA": true,
"isCA": false,
"maxPathLen": 0
},
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z"
}`

// TSA intermediate template
intermediateContent := `{
"subject": {
"commonName": "https://blah.com"
},
"issuer": {
"commonName": "https://blah.com"
},
"keyUsage": [
"certSign",
"crlSign"
"digitalSignature"
"digitalSignature"
],
"basicConstraints": {
"isCA": false
},
"extensions": [
{
"id": "2.5.29.37",
"critical": true,
"value": "asn1Seq (asn1Enc oid:1.3.6.1.5.5.7.3.8) | toJson"
"value": {{ asn1Seq (asn1Enc "oid:1.3.6.1.5.5.7.3.8") | toJson }}
"value": {{ asn1Seq (asn1Enc "oid:1.3.6.1.5.5.7.3.8") | toJson }}
}
],
"notBefore": "2024-01-01T00:00:00Z",
"notAfter": "2025-01-01T00:00:00Z"
]
]
}`

testCertificateCreation(t, tmpDir, rootContent, intermediateContent)
Expand Down
Loading

0 comments on commit 38d5a1d

Please sign in to comment.