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
|
successes := 0
|
||||||
errors := 0
|
errors := 0
|
||||||
|
var basename, certFile, keyFile string // Declare variables before the loop
|
||||||
for i, def := range certDefs {
|
for i, def := range certDefs {
|
||||||
if defaults != nil {
|
if defaults != nil {
|
||||||
if def.Type == "" {
|
if def.Type == "" {
|
||||||
@@ -315,23 +316,161 @@ func IssueCertificate(configPath, subject, certType, validity string, san []stri
|
|||||||
fmt.Printf(" SAN: %v\n\n", finalDef.SAN)
|
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 {
|
if dryRun {
|
||||||
successes++
|
successes++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := IssueCertificateWithBasename(configPath, basename, finalDef.Subject, finalDef.Type, finalDef.Validity, finalDef.SAN, overwrite, dryRun)
|
// Inline certificate issuance logic for batch mode
|
||||||
if err != nil {
|
// Add default dns SAN for server/server-only if none specified
|
||||||
fmt.Printf("ERROR: %v\n", err)
|
if (finalDef.Type == "server" || finalDef.Type == "server-only") && len(finalDef.SAN) == 0 {
|
||||||
errors++
|
finalDef.SAN = append(finalDef.SAN, "dns:"+finalDef.Subject)
|
||||||
} else {
|
|
||||||
if !verbose {
|
|
||||||
fmt.Printf("done\n")
|
|
||||||
}
|
|
||||||
successes++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
fmt.Printf("Batch complete: %d succeeded, %d failed.\n", successes, errors)
|
||||||
// Save CA state after batch issuance
|
// 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(" Validity: %s\n", finalDef.Validity)
|
||||||
fmt.Printf(" SAN: %v\n", finalDef.SAN)
|
fmt.Printf(" SAN: %v\n", finalDef.SAN)
|
||||||
}
|
}
|
||||||
internalIssueCertificate(configPath, finalDef.Name, finalDef.Subject, finalDef.Type, finalDef.Validity, finalDef.SAN, overwrite)
|
// Inline the logic from internalIssueCertificate here
|
||||||
// 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) {
|
|
||||||
// Add default dns SAN for server/server-only if none specified
|
// Add default dns SAN for server/server-only if none specified
|
||||||
if (certType == "server" || certType == "server-only") && len(san) == 0 {
|
if (finalDef.Type == "server" || finalDef.Type == "server-only") && len(finalDef.SAN) == 0 {
|
||||||
san = append(san, "dns:"+subject)
|
finalDef.SAN = append(finalDef.SAN, "dns:"+finalDef.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
ca, err := LoadCA(configPath)
|
ca, err = LoadCA(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error loading config:", err)
|
fmt.Println("Error loading config:", err)
|
||||||
return
|
return
|
||||||
@@ -426,35 +555,35 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var validity time.Duration
|
var validityDur time.Duration
|
||||||
if validityFlag != "" {
|
if finalDef.Validity != "" {
|
||||||
validity, err = parseValidity(validityFlag)
|
validityDur, err = parseValidity(finalDef.Validity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Invalid validity value:", err)
|
fmt.Println("Invalid validity value:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Parse subject as DN if it looks like a DN, otherwise use as CommonName only
|
||||||
var subjectPKIX pkix.Name
|
var subjectPKIX pkix.Name
|
||||||
if isDNFormat(subject) {
|
if isDNFormat(finalDef.Subject) {
|
||||||
subjectPKIX = parseDistinguishedName(subject)
|
subjectPKIX = parseDistinguishedName(finalDef.Subject)
|
||||||
} else {
|
} else {
|
||||||
subjectPKIX = pkix.Name{CommonName: subject}
|
subjectPKIX = pkix.Name{CommonName: finalDef.Subject}
|
||||||
}
|
}
|
||||||
|
|
||||||
certTmpl := x509.Certificate{
|
certTmpl := x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
Subject: subjectPKIX,
|
Subject: subjectPKIX,
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: time.Now().Add(validity),
|
NotAfter: time.Now().Add(validityDur),
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle SANs
|
// Handle SANs
|
||||||
for _, s := range san {
|
for _, s := range finalDef.SAN {
|
||||||
sLower := strings.ToLower(s)
|
sLower := strings.ToLower(s)
|
||||||
var val string
|
var val string
|
||||||
if n, _ := fmt.Sscanf(sLower, "dns:%s", &val); n == 1 {
|
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":
|
case "client":
|
||||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||||
case "server":
|
case "server":
|
||||||
@@ -493,12 +622,12 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
|||||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||||
|
|
||||||
basename := name
|
basename := finalDef.Name
|
||||||
if basename == "" {
|
if basename == "" {
|
||||||
basename = subject
|
basename = finalDef.Subject
|
||||||
}
|
}
|
||||||
certFile := filepath.Join(ca.Paths.Certificates, basename+"."+certType+".crt.pem")
|
certFile := filepath.Join(ca.Paths.Certificates, basename+"."+finalDef.Type+".crt.pem")
|
||||||
keyFile := filepath.Join(ca.Paths.PrivateKeys, basename+"."+certType+".key.pem")
|
keyFile := filepath.Join(ca.Paths.PrivateKeys, basename+"."+finalDef.Type+".key.pem")
|
||||||
if err := SavePEM(certFile, certPEM, false, overwrite); err != nil {
|
if err := SavePEM(certFile, certPEM, false, overwrite); err != nil {
|
||||||
fmt.Println("Error saving certificate:", err)
|
fmt.Println("Error saving certificate:", err)
|
||||||
return
|
return
|
||||||
@@ -507,7 +636,14 @@ func internalIssueCertificate(configPath, name string, subject, certType, validi
|
|||||||
fmt.Println("Error saving key:", err)
|
fmt.Println("Error saving key:", err)
|
||||||
return
|
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)
|
// 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=...)
|
// Helper: check if string looks like a DN (contains at least CN=...)
|
||||||
func isDNFormat(s string) bool {
|
func isDNFormat(s string) bool {
|
||||||
return len(s) > 0 && strings.Contains(s, "CN=")
|
return len(s) > 0 && strings.Contains(s, "CN=")
|
||||||
|
Reference in New Issue
Block a user