diff --git a/cmd/tfarm/commands/server/certs-client.go b/cmd/tfarm/commands/server/certs-client.go index b2a2d6e..3e37c3a 100644 --- a/cmd/tfarm/commands/server/certs-client.go +++ b/cmd/tfarm/commands/server/certs-client.go @@ -1,19 +1,9 @@ package server import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" - "io" - "math/big" "os" "path" - "path/filepath" - "time" "github.com/cbodonnell/tfarm/pkg/certs" "github.com/spf13/cobra" @@ -47,95 +37,5 @@ func CertsClient(name string) error { workDir = pwd } - fmt.Println("Generating client certificate...") - - // read the ca.key and ca.crt files from the workDir/tls directory - caKeyFile, err := os.Open(path.Join(workDir, "tls", "ca.key")) - if err != nil { - return fmt.Errorf("error opening CA key file: %s", err) - } - defer caKeyFile.Close() - caKeyPEM, err := io.ReadAll(caKeyFile) - if err != nil { - return fmt.Errorf("error reading CA key file: %s", err) - } - caKeyBlock, _ := pem.Decode(caKeyPEM) - caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) - if err != nil { - return fmt.Errorf("error parsing CA key: %s", err) - } - - caFile, err := os.Open(path.Join(workDir, "tls", "ca.crt")) - if err != nil { - return fmt.Errorf("error opening CA file: %s", err) - } - defer caFile.Close() - caCertPEM, err := io.ReadAll(caFile) - if err != nil { - return fmt.Errorf("error reading CA file: %s", err) - } - caCertBlock, _ := pem.Decode(caCertPEM) - caCert, err := x509.ParseCertificate(caCertBlock.Bytes) - if err != nil { - return fmt.Errorf("error parsing CA certificate: %s", err) - } - - // Generate a new client certificate/key pair - clientTemplate := x509.Certificate{ - SerialNumber: big.NewInt(3), - Subject: pkix.Name{ - CommonName: name, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(1, 0, 0), // Valid for 1 year - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - SignatureAlgorithm: x509.SHA256WithRSA, - Issuer: caCert.Subject, - BasicConstraintsValid: true, - } - clientKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return fmt.Errorf("error generating client key: %s", err) - } - - clientCert, err := x509.CreateCertificate(rand.Reader, &clientTemplate, caCert, &clientKey.PublicKey, caKey) - if err != nil { - return fmt.Errorf("error generating client certificate: %s", err) - } - - // make sure the tls/clients directory exists - err = os.MkdirAll(path.Join(workDir, "tls", "clients", name), 0755) - if err != nil { - return fmt.Errorf("error creating clients directory: %s", err) - } - - certBuff := bytes.NewBuffer(nil) - if err := pem.Encode(certBuff, &pem.Block{Type: "CERTIFICATE", Bytes: clientCert}); err != nil { - return fmt.Errorf("error pem encoding client certificate: %s", err) - } - - keyBuff := bytes.NewBuffer(nil) - if err := pem.Encode(keyBuff, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey)}); err != nil { - return fmt.Errorf("error pem encoding client key: %s", err) - } - - client := &certs.Client{ - CA: caCertPEM, - Cert: certBuff.Bytes(), - Key: keyBuff.Bytes(), - } - if err := client.SaveToFile(path.Join(workDir, "tls", "clients", name, "client.json")); err != nil { - return fmt.Errorf("error saving client to file: %s", err) - } - - absPath, err := filepath.Abs(path.Join(workDir, "tls", "clients", name)) - if err != nil { - return fmt.Errorf("error getting absolute path of client certificate: %s", err) - } - - fmt.Println("Client certificate and key written to:") - fmt.Println(" ", path.Join(absPath, "client.json")) - - return nil + return certs.GenerateClientCerts(path.Join(workDir, "tls"), name) } diff --git a/cmd/tfarm/commands/server/certs-regenerate.go b/cmd/tfarm/commands/server/certs-regenerate.go index f550e14..f00f60c 100644 --- a/cmd/tfarm/commands/server/certs-regenerate.go +++ b/cmd/tfarm/commands/server/certs-regenerate.go @@ -33,5 +33,5 @@ func CertsRegenerate() error { workDir = pwd } - return certs.GenerateCerts(path.Join(workDir, "tls")) + return certs.GenerateServerCerts(path.Join(workDir, "tls")) } diff --git a/cmd/tfarm/commands/server/start.go b/cmd/tfarm/commands/server/start.go index 4b677e1..1ba58c9 100644 --- a/cmd/tfarm/commands/server/start.go +++ b/cmd/tfarm/commands/server/start.go @@ -96,7 +96,7 @@ func Start(port int, cfg config.ClientCommonConf) error { if _, err := os.Stat(tlsDir); err != nil { if os.IsNotExist(err) { log.Println("tls directory not found, generating certificates") - if err := certs.GenerateCerts(tlsDir); err != nil { + if err := certs.GenerateServerCerts(tlsDir); err != nil { return fmt.Errorf("error generating tls certificates: %s", err) } } else { diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go index 220ac31..d7c24c2 100644 --- a/pkg/certs/certs.go +++ b/pkg/certs/certs.go @@ -17,7 +17,7 @@ import ( ) // TODO: Split this into a CA, server, and client cert generator -func GenerateCerts(dir string) error { +func GenerateServerCerts(dir string) error { fmt.Println("Generating CA certificate...") // Generate a new CA certificate diff --git a/pkg/certs/client.go b/pkg/certs/client.go index 6cb2a05..4d09132 100644 --- a/pkg/certs/client.go +++ b/pkg/certs/client.go @@ -1,11 +1,21 @@ package certs import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" "encoding/base64" "encoding/json" + "encoding/pem" "fmt" "io" + "math/big" "os" + "path" + "path/filepath" + "time" ) type Client struct { @@ -67,3 +77,97 @@ func (c *Client) SaveToFile(path string) error { } return nil } + +func GenerateClientCerts(dir string, name string) error { + fmt.Println("Generating client certificate...") + + // read the ca.key and ca.crt files from the workDir/tls directory + caKeyFile, err := os.Open(path.Join(dir, "ca.key")) + if err != nil { + return fmt.Errorf("error opening CA key file: %s", err) + } + defer caKeyFile.Close() + caKeyPEM, err := io.ReadAll(caKeyFile) + if err != nil { + return fmt.Errorf("error reading CA key file: %s", err) + } + caKeyBlock, _ := pem.Decode(caKeyPEM) + caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) + if err != nil { + return fmt.Errorf("error parsing CA key: %s", err) + } + + caFile, err := os.Open(path.Join(dir, "ca.crt")) + if err != nil { + return fmt.Errorf("error opening CA file: %s", err) + } + defer caFile.Close() + caCertPEM, err := io.ReadAll(caFile) + if err != nil { + return fmt.Errorf("error reading CA file: %s", err) + } + caCertBlock, _ := pem.Decode(caCertPEM) + caCert, err := x509.ParseCertificate(caCertBlock.Bytes) + if err != nil { + return fmt.Errorf("error parsing CA certificate: %s", err) + } + + // Generate a new client certificate/key pair + clientTemplate := x509.Certificate{ + SerialNumber: big.NewInt(3), + Subject: pkix.Name{ + CommonName: name, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), // Valid for 1 year + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + SignatureAlgorithm: x509.SHA256WithRSA, + Issuer: caCert.Subject, + BasicConstraintsValid: true, + } + clientKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return fmt.Errorf("error generating client key: %s", err) + } + + clientCert, err := x509.CreateCertificate(rand.Reader, &clientTemplate, caCert, &clientKey.PublicKey, caKey) + if err != nil { + return fmt.Errorf("error generating client certificate: %s", err) + } + + // make sure the tls/clients directory exists + err = os.MkdirAll(path.Join(dir, "clients", name), 0755) + if err != nil { + return fmt.Errorf("error creating clients directory: %s", err) + } + + certBuff := bytes.NewBuffer(nil) + if err := pem.Encode(certBuff, &pem.Block{Type: "CERTIFICATE", Bytes: clientCert}); err != nil { + return fmt.Errorf("error pem encoding client certificate: %s", err) + } + + keyBuff := bytes.NewBuffer(nil) + if err := pem.Encode(keyBuff, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey)}); err != nil { + return fmt.Errorf("error pem encoding client key: %s", err) + } + + client := &Client{ + CA: caCertPEM, + Cert: certBuff.Bytes(), + Key: keyBuff.Bytes(), + } + if err := client.SaveToFile(path.Join(dir, "clients", name, "client.json")); err != nil { + return fmt.Errorf("error saving client to file: %s", err) + } + + absPath, err := filepath.Abs(path.Join(dir, "clients", name)) + if err != nil { + return fmt.Errorf("error getting absolute path of client certificate: %s", err) + } + + fmt.Println("Client certificate and key written to:") + fmt.Println(" ", path.Join(absPath, "client.json")) + + return nil +}