Refactored code to one IssueCertificate function.
This commit is contained in:
350
ca.go
350
ca.go
@@ -287,6 +287,7 @@ func IssueCertificate(configPath, subject, certType, validity string, san []stri
|
||||
}
|
||||
successes := 0
|
||||
errors := 0
|
||||
var basename, certFile, keyFile string // Declare variables before the loop
|
||||
for i, def := range certDefs {
|
||||
if defaults != nil {
|
||||
if def.Type == "" {
|
||||
@@ -315,23 +316,161 @@ func IssueCertificate(configPath, subject, certType, validity string, san []stri
|
||||
fmt.Printf(" SAN: %v\n\n", finalDef.SAN)
|
||||
}
|
||||
|
||||
basename := finalDef.Name + "." + finalDef.Type
|
||||
basename = finalDef.Name
|
||||
if basename == "" {
|
||||
basename = finalDef.Subject
|
||||
}
|
||||
certFile = filepath.Join(ca.Paths.Certificates, basename+"."+finalDef.Type+".crt.pem")
|
||||
keyFile = filepath.Join(ca.Paths.PrivateKeys, basename+"."+finalDef.Type+".key.pem")
|
||||
|
||||
if dryRun {
|
||||
successes++
|
||||
continue
|
||||
}
|
||||
|
||||
err := IssueCertificateWithBasename(configPath, basename, finalDef.Subject, finalDef.Type, finalDef.Validity, finalDef.SAN, overwrite, dryRun)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %v\n", err)
|
||||
errors++
|
||||
} else {
|
||||
if !verbose {
|
||||
fmt.Printf("done\n")
|
||||
}
|
||||
successes++
|
||||
// Inline certificate issuance logic for batch mode
|
||||
// Add default dns SAN for server/server-only if none specified
|
||||
if (finalDef.Type == "server" || finalDef.Type == "server-only") && len(finalDef.SAN) == 0 {
|
||||
finalDef.SAN = append(finalDef.SAN, "dns:"+finalDef.Subject)
|
||||
}
|
||||
|
||||
caCertPath := filepath.Join(ca.Paths.Certificates, "ca_cert.pem")
|
||||
caKeyPath := filepath.Join(ca.Paths.PrivateKeys, "ca_key.pem")
|
||||
|
||||
caCertPEM, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading CA certificate file:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
caKeyPEM, err := os.ReadFile(caKeyPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading CA key file:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
caCertBlock, _ := pem.Decode(caCertPEM)
|
||||
if caCertBlock == nil {
|
||||
fmt.Println("Failed to parse CA certificate PEM")
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to parse CA certificate:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
caKeyBlock, _ := pem.Decode(caKeyPEM)
|
||||
if caKeyBlock == nil {
|
||||
fmt.Println("Failed to parse CA key PEM")
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to parse CA private key:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to generate private key:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
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)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
var validityDur time.Duration
|
||||
if finalDef.Validity != "" {
|
||||
validityDur, err = parseValidity(finalDef.Validity)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid validity value:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
validityDur = 365 * 24 * time.Hour // default 1 year
|
||||
}
|
||||
|
||||
var subjectPKIX pkix.Name
|
||||
if isDNFormat(finalDef.Subject) {
|
||||
subjectPKIX = parseDistinguishedName(finalDef.Subject)
|
||||
} else {
|
||||
subjectPKIX = pkix.Name{CommonName: finalDef.Subject}
|
||||
}
|
||||
|
||||
certTmpl := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: subjectPKIX,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(validityDur),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
}
|
||||
|
||||
for _, s := range finalDef.SAN {
|
||||
sLower := strings.ToLower(s)
|
||||
var val string
|
||||
if n, _ := fmt.Sscanf(sLower, "dns:%s", &val); n == 1 {
|
||||
certTmpl.DNSNames = append(certTmpl.DNSNames, val)
|
||||
} else if n, _ := fmt.Sscanf(sLower, "ip:%s", &val); n == 1 {
|
||||
certTmpl.IPAddresses = append(certTmpl.IPAddresses, net.ParseIP(val))
|
||||
} else if n, _ := fmt.Sscanf(sLower, "email:%s", &val); n == 1 {
|
||||
certTmpl.EmailAddresses = append(certTmpl.EmailAddresses, val)
|
||||
} else {
|
||||
fmt.Printf("Invalid SAN format: %s\n", s)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch finalDef.Type {
|
||||
case "client":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||
case "server":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
|
||||
case "server-only":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||
case "code-signing":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}
|
||||
case "email":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}
|
||||
default:
|
||||
fmt.Println("Unknown certificate type. Use one of: client, server, server-only, code-signing, email.")
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, &priv.PublicKey, caKey)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to create certificate:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
if err := SavePEM(certFile, certPEM, false, overwrite); err != nil {
|
||||
fmt.Println("Error saving certificate:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
if err := SavePEM(keyFile, keyPEM, true, overwrite); err != nil {
|
||||
fmt.Println("Error saving key:", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
if !verbose {
|
||||
fmt.Printf("done\n")
|
||||
}
|
||||
successes++
|
||||
}
|
||||
fmt.Printf("Batch complete: %d succeeded, %d failed.\n", successes, errors)
|
||||
// Save CA state after batch issuance
|
||||
@@ -357,23 +496,13 @@ func IssueCertificate(configPath, subject, certType, validity string, san []stri
|
||||
fmt.Printf(" Validity: %s\n", finalDef.Validity)
|
||||
fmt.Printf(" SAN: %v\n", finalDef.SAN)
|
||||
}
|
||||
internalIssueCertificate(configPath, finalDef.Name, finalDef.Subject, finalDef.Type, finalDef.Validity, finalDef.SAN, overwrite)
|
||||
// Save CA state after single issuance
|
||||
caDir := filepath.Dir(configPath)
|
||||
caLabel := ca.Label
|
||||
caStatePath := filepath.Join(caDir, caLabel+"_state.json")
|
||||
if err := SaveCAState(caStatePath, GlobalCAState); err != nil {
|
||||
fmt.Printf("Error saving CA state: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func internalIssueCertificate(configPath, name string, subject, certType, validityFlag string, san []string, overwrite bool) {
|
||||
// Inline the logic from internalIssueCertificate here
|
||||
// Add default dns SAN for server/server-only if none specified
|
||||
if (certType == "server" || certType == "server-only") && len(san) == 0 {
|
||||
san = append(san, "dns:"+subject)
|
||||
if (finalDef.Type == "server" || finalDef.Type == "server-only") && len(finalDef.SAN) == 0 {
|
||||
finalDef.SAN = append(finalDef.SAN, "dns:"+finalDef.Subject)
|
||||
}
|
||||
|
||||
ca, err := LoadCA(configPath)
|
||||
ca, err = LoadCA(configPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error loading config:", err)
|
||||
return
|
||||
@@ -426,35 +555,35 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
||||
return
|
||||
}
|
||||
|
||||
var validity time.Duration
|
||||
if validityFlag != "" {
|
||||
validity, err = parseValidity(validityFlag)
|
||||
var validityDur time.Duration
|
||||
if finalDef.Validity != "" {
|
||||
validityDur, err = parseValidity(finalDef.Validity)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid validity value:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
validity = 365 * 24 * time.Hour // default 1 year
|
||||
validityDur = 365 * 24 * time.Hour // default 1 year
|
||||
}
|
||||
|
||||
// Parse subject as DN if it looks like a DN, otherwise use as CommonName only
|
||||
var subjectPKIX pkix.Name
|
||||
if isDNFormat(subject) {
|
||||
subjectPKIX = parseDistinguishedName(subject)
|
||||
if isDNFormat(finalDef.Subject) {
|
||||
subjectPKIX = parseDistinguishedName(finalDef.Subject)
|
||||
} else {
|
||||
subjectPKIX = pkix.Name{CommonName: subject}
|
||||
subjectPKIX = pkix.Name{CommonName: finalDef.Subject}
|
||||
}
|
||||
|
||||
certTmpl := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: subjectPKIX,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
NotAfter: time.Now().Add(validityDur),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
}
|
||||
|
||||
// Handle SANs
|
||||
for _, s := range san {
|
||||
for _, s := range finalDef.SAN {
|
||||
sLower := strings.ToLower(s)
|
||||
var val string
|
||||
if n, _ := fmt.Sscanf(sLower, "dns:%s", &val); n == 1 {
|
||||
@@ -469,7 +598,7 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
||||
}
|
||||
}
|
||||
|
||||
switch certType {
|
||||
switch finalDef.Type {
|
||||
case "client":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||
case "server":
|
||||
@@ -493,12 +622,12 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
|
||||
basename := name
|
||||
basename := finalDef.Name
|
||||
if basename == "" {
|
||||
basename = subject
|
||||
basename = finalDef.Subject
|
||||
}
|
||||
certFile := filepath.Join(ca.Paths.Certificates, basename+"."+certType+".crt.pem")
|
||||
keyFile := filepath.Join(ca.Paths.PrivateKeys, basename+"."+certType+".key.pem")
|
||||
certFile := filepath.Join(ca.Paths.Certificates, basename+"."+finalDef.Type+".crt.pem")
|
||||
keyFile := filepath.Join(ca.Paths.PrivateKeys, basename+"."+finalDef.Type+".key.pem")
|
||||
if err := SavePEM(certFile, certPEM, false, overwrite); err != nil {
|
||||
fmt.Println("Error saving certificate:", err)
|
||||
return
|
||||
@@ -507,7 +636,14 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
||||
fmt.Println("Error saving key:", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s certificate and key for '%s' generated.\n", certType, subject)
|
||||
fmt.Printf("%s certificate and key for '%s' generated.\n", finalDef.Type, finalDef.Subject)
|
||||
// Save CA state after single issuance
|
||||
caDir := filepath.Dir(configPath)
|
||||
caLabel := ca.Label
|
||||
caStatePath := filepath.Join(caDir, caLabel+"_state.json")
|
||||
if err := SaveCAState(caStatePath, GlobalCAState); err != nil {
|
||||
fmt.Printf("Error saving CA state: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract defaults from certificates.hcl (now using new LoadCertificatesFile signature)
|
||||
@@ -523,142 +659,6 @@ func GetCertificateDefaults(path string) CertificateDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
// Issue certificate with custom basename and dry-run support
|
||||
func IssueCertificateWithBasename(configPath, basename, subject, certType, validityFlag string, san []string, overwrite, dryRun bool) error {
|
||||
if dryRun {
|
||||
fmt.Printf("Would issue certificate: name=%s, subject=%s, type=%s, validity=%s, SAN=%v\n", basename, subject, certType, validityFlag, san)
|
||||
return nil
|
||||
}
|
||||
// Call IssueCertificate but override basename logic
|
||||
return issueCertificateInternal(configPath, basename, subject, certType, validityFlag, san, overwrite)
|
||||
}
|
||||
|
||||
// Internal: like IssueCertificate but with explicit basename
|
||||
func issueCertificateInternal(configPath, basename, subject, certType, validityFlag string, san []string, overwrite bool) error {
|
||||
// Add default dns SAN for server/server-only if none specified
|
||||
if (certType == "server" || certType == "server-only") && len(san) == 0 {
|
||||
san = append(san, "dns:"+subject)
|
||||
}
|
||||
|
||||
ca, err := LoadCA(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading config: %v", err)
|
||||
}
|
||||
|
||||
caCertPath := filepath.Join(ca.Paths.Certificates, "ca_cert.pem")
|
||||
caKeyPath := filepath.Join(ca.Paths.PrivateKeys, "ca_key.pem")
|
||||
|
||||
caCertPEM, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading CA certificate file: %v", err)
|
||||
}
|
||||
caKeyPEM, err := os.ReadFile(caKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading CA key file: %v", err)
|
||||
}
|
||||
|
||||
caCertBlock, _ := pem.Decode(caCertPEM)
|
||||
if caCertBlock == nil {
|
||||
return fmt.Errorf("Failed to parse CA certificate PEM")
|
||||
}
|
||||
caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse CA certificate: %v", err)
|
||||
}
|
||||
caKeyBlock, _ := pem.Decode(caKeyPEM)
|
||||
if caKeyBlock == nil {
|
||||
return fmt.Errorf("Failed to parse CA key PEM")
|
||||
}
|
||||
caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse CA private key: %v", err)
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to generate private key: %v", err)
|
||||
}
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to generate serial number: %v", err)
|
||||
}
|
||||
|
||||
var validity time.Duration
|
||||
if validityFlag != "" {
|
||||
validity, err = parseValidity(validityFlag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid validity value: %v", err)
|
||||
}
|
||||
} else {
|
||||
validity = 365 * 24 * time.Hour // default 1 year
|
||||
}
|
||||
|
||||
// Parse subject as DN if it looks like a DN, otherwise use as CommonName only
|
||||
var subjectPKIX pkix.Name
|
||||
if isDNFormat(subject) {
|
||||
subjectPKIX = parseDistinguishedName(subject)
|
||||
} else {
|
||||
subjectPKIX = pkix.Name{CommonName: subject}
|
||||
}
|
||||
|
||||
certTmpl := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: subjectPKIX,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
}
|
||||
|
||||
// Handle SANs
|
||||
for _, s := range san {
|
||||
sLower := strings.ToLower(s)
|
||||
var val string
|
||||
if n, _ := fmt.Sscanf(sLower, "dns:%s", &val); n == 1 {
|
||||
certTmpl.DNSNames = append(certTmpl.DNSNames, val)
|
||||
} else if n, _ := fmt.Sscanf(sLower, "ip:%s", &val); n == 1 {
|
||||
certTmpl.IPAddresses = append(certTmpl.IPAddresses, net.ParseIP(val))
|
||||
} else if n, _ := fmt.Sscanf(sLower, "email:%s", &val); n == 1 {
|
||||
certTmpl.EmailAddresses = append(certTmpl.EmailAddresses, val)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid SAN format: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
switch certType {
|
||||
case "client":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||
case "server":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
|
||||
case "server-only":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||
case "code-signing":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}
|
||||
case "email":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}
|
||||
default:
|
||||
return fmt.Errorf("Unknown certificate type. Use one of: client, server, server-only, code-signing, email.")
|
||||
}
|
||||
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, &priv.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create certificate: %v", err)
|
||||
}
|
||||
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(ca.Paths.Certificates, basename+".crt.pem")
|
||||
keyFile := filepath.Join(ca.Paths.PrivateKeys, basename+".key.pem")
|
||||
if err := SavePEM(certFile, certPEM, false, overwrite); err != nil {
|
||||
return fmt.Errorf("Error saving certificate: %v", err)
|
||||
}
|
||||
if err := SavePEM(keyFile, keyPEM, true, overwrite); err != nil {
|
||||
return fmt.Errorf("Error saving key: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper: check if string looks like a DN (contains at least CN=...)
|
||||
func isDNFormat(s string) bool {
|
||||
return len(s) > 0 && strings.Contains(s, "CN=")
|
||||
|
Reference in New Issue
Block a user