diff --git a/ca.go b/ca.go index 9d4758b..0f9ae95 100644 --- a/ca.go +++ b/ca.go @@ -9,18 +9,25 @@ import ( "fmt" "math/big" "os" + "path/filepath" "time" gohcl "github.com/hashicorp/hcl/v2/gohcl" hclparse "github.com/hashicorp/hcl/v2/hclparse" ) +type PathsConfig struct { + Certificates string `hcl:"certificates"` + PrivateKeys string `hcl:"private_keys"` +} + type CAConfig struct { - Label string `hcl:",label"` - Name string `hcl:"name"` - Country string `hcl:"country"` - Organization string `hcl:"organization"` - SerialType string `hcl:"serial_type"` + Label string `hcl:",label"` + Name string `hcl:"name"` + Country string `hcl:"country"` + Organization string `hcl:"organization"` + SerialType string `hcl:"serial_type"` + Paths PathsConfig `hcl:"paths,block"` } type ConfigFile struct { @@ -96,6 +103,16 @@ func SavePEM(filename string, data []byte, secure bool, overwrite bool) error { } } +func (p *PathsConfig) Validate() error { + if p.Certificates == "" { + return fmt.Errorf("paths.certificates is required") + } + if p.PrivateKeys == "" { + return fmt.Errorf("paths.private_keys is required") + } + return nil +} + func (c *CAConfig) Validate() error { if c.Name == "" { return fmt.Errorf("CA 'name' is required") @@ -112,5 +129,127 @@ func (c *CAConfig) Validate() error { if c.SerialType != "random" && c.SerialType != "sequential" { return fmt.Errorf("CA 'serial_type' must be 'random' or 'sequential'") } + if err := c.Paths.Validate(); err != nil { + return err + } return nil } + +func InitCA(configPath string, overwrite bool) { + config, err := LoadCAConfig(configPath) + if err != nil { + fmt.Println("Error loading config:", err) + return + } + + // Create directories for certificates and private keys if they don't exist + dirs := []string{config.Paths.Certificates, config.Paths.PrivateKeys} + for _, dir := range dirs { + if dir == "" { + continue + } + if err := os.MkdirAll(dir, 0700); err != nil { + fmt.Printf("Error creating directory '%s': %v\n", dir, err) + return + } + } + + certPEM, keyPEM, err := GenerateCA(config) + if err != nil { + fmt.Println("Error generating CA:", err) + return + } + if err := SavePEM(filepath.Join(config.Paths.Certificates, "ca_cert.pem"), certPEM, false, overwrite); err != nil { + fmt.Println("Error saving CA certificate:", err) + return + } + if err := SavePEM(filepath.Join(config.Paths.PrivateKeys, "ca_key.pem"), keyPEM, true, overwrite); err != nil { + fmt.Println("Error saving CA key:", err) + return + } + fmt.Println("CA certificate and key generated.") +} + +func IssueCertificate(configPath, subject string, overwrite bool) { + config, err := LoadCAConfig(configPath) + if err != nil { + fmt.Println("Error loading config:", err) + return + } + + caCertPath := filepath.Join(config.Paths.Certificates, "ca_cert.pem") + caKeyPath := filepath.Join(config.Paths.PrivateKeys, "ca_key.pem") + + caCertPEM, err := os.ReadFile(caCertPath) + if err != nil { + fmt.Println("Error reading CA certificate file:", err) + return + } + caKeyPEM, err := os.ReadFile(caKeyPath) + if err != nil { + fmt.Println("Error reading CA key file:", err) + return + } + + caCertBlock, _ := pem.Decode(caCertPEM) + if caCertBlock == nil { + fmt.Println("Failed to parse CA certificate PEM") + return + } + caCert, err := x509.ParseCertificate(caCertBlock.Bytes) + if err != nil { + fmt.Println("Failed to parse CA certificate:", err) + return + } + caKeyBlock, _ := pem.Decode(caKeyPEM) + if caKeyBlock == nil { + fmt.Println("Failed to parse CA key PEM") + return + } + caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) + if err != nil { + fmt.Println("Failed to parse CA private key:", err) + return + } + + priv, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + fmt.Println("Failed to generate private key:", err) + return + } + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + fmt.Println("Failed to generate serial number:", err) + return + } + certTmpl := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: subject, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), // 1 year + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + } + certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, &priv.PublicKey, caKey) + if err != nil { + fmt.Println("Failed to create certificate:", err) + return + } + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + + certFile := filepath.Join(config.Paths.Certificates, subject+".crt.pem") + keyFile := filepath.Join(config.Paths.PrivateKeys, subject+".key.pem") + if err := SavePEM(certFile, certPEM, false, overwrite); err != nil { + fmt.Println("Error saving certificate:", err) + return + } + if err := SavePEM(keyFile, keyPEM, true, overwrite); err != nil { + fmt.Println("Error saving key:", err) + return + } + fmt.Printf("Certificate and key for '%s' generated.\n", subject) +} diff --git a/main.go b/main.go index 348766e..51b4859 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,8 @@ package main import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" - "math/big" "os" - "time" "github.com/spf13/cobra" ) @@ -32,7 +25,7 @@ func main() { Use: "initca", Short: "Generate a new CA certificate and key", Run: func(cmd *cobra.Command, args []string) { - handleInitCA(configPath, overwrite) + InitCA(configPath, overwrite) }, } @@ -43,7 +36,7 @@ func main() { Use: "issue", Short: "Issue a new client/server certificate", Run: func(cmd *cobra.Command, args []string) { - handleIssue(configPath, subject, overwrite) + IssueCertificate(configPath, subject, overwrite) }, } @@ -60,106 +53,6 @@ func main() { } } -func handleInitCA(configPath string, overwrite bool) { - config, err := LoadCAConfig(configPath) - if err != nil { - fmt.Println("Error loading config:", err) - return - } - certPEM, keyPEM, err := GenerateCA(config) - if err != nil { - fmt.Println("Error generating CA:", err) - return - } - if err := SavePEM("ca_cert.pem", certPEM, false, overwrite); err != nil { - fmt.Println("Error saving CA certificate:", err) - return - } - if err := SavePEM("ca_key.pem", keyPEM, true, overwrite); err != nil { - fmt.Println("Error saving CA key:", err) - return - } - fmt.Println("CA certificate and key generated.") -} - -func handleIssue(configPath, subject string, overwrite bool) { - // Load existing CA certificate and key from files - caCertPEM, err := os.ReadFile("ca_cert.pem") - if err != nil { - fmt.Println("Error reading CA certificate file:", err) - return - } - caKeyPEM, err := os.ReadFile("ca_key.pem") - if err != nil { - fmt.Println("Error reading CA key file:", err) - return - } - - // Parse CA cert and key - caCertBlock, _ := pem.Decode(caCertPEM) - if caCertBlock == nil { - fmt.Println("Failed to parse CA certificate PEM") - return - } - caCert, err := x509.ParseCertificate(caCertBlock.Bytes) - if err != nil { - fmt.Println("Failed to parse CA certificate:", err) - return - } - caKeyBlock, _ := pem.Decode(caKeyPEM) - if caKeyBlock == nil { - fmt.Println("Failed to parse CA key PEM") - return - } - caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) - if err != nil { - fmt.Println("Failed to parse CA private key:", err) - return - } - - // Generate client/server key - priv, err := rsa.GenerateKey(rand.Reader, 4096) - if err != nil { - fmt.Println("Failed to generate private key:", err) - return - } - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - fmt.Println("Failed to generate serial number:", err) - return - } - certTmpl := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: subject, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(1, 0, 0), // 1 year - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - } - certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, &priv.PublicKey, caKey) - if err != nil { - fmt.Println("Failed to create certificate:", err) - return - } - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) - - certFile := subject + ".crt.pem" - keyFile := subject + ".key.pem" - if err := SavePEM(certFile, certPEM, false, overwrite); err != nil { - fmt.Println("Error saving certificate:", err) - return - } - if err := SavePEM(keyFile, keyPEM, true, overwrite); err != nil { - fmt.Println("Error saving key:", err) - return - } - fmt.Printf("Certificate and key for '%s' generated.\n", subject) -} - func printMainHelp() { fmt.Println("lab-ca - Certificate Authority Utility") fmt.Println()