Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add option to also write client key in PKCS#8 format. #29008

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 2 additions & 10 deletions pkg/acceptance/cluster/certs.go
Expand Up @@ -42,19 +42,11 @@ func GenerateCerts(ctx context.Context) func() {

maybePanic(security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedCAKey),
512, 48*time.Hour, false, security.RootUser))
512, 48*time.Hour, false, security.RootUser, true /* generate pk8 key */))

maybePanic(security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedCAKey),
512, 48*time.Hour, false, "testuser"))

// Store a copy of the client private key in PKCS#8 format, which is
// the only format understood by PgJDBC (Java).
{
execCmd("openssl", "pkcs8", "-topk8", "-outform", "DER", "-nocrypt",
"-in", filepath.Join(certsDir, "client.root.key"),
"-out", filepath.Join(certsDir, "client.root.pk8"))
}
512, 48*time.Hour, false, "testuser", true /* generate pk8 key */))

// Store a copy of the client certificate and private key in a PKCS#12
// bundle, which is the only format understood by Npgsql (.NET).
Expand Down
Expand Up @@ -33,7 +33,7 @@ public String getDBUrl() {
if (System.getenv("PGSSLCERT") != null) {
DBUrl += "?ssl=true";
DBUrl += "&sslcert=" + System.getenv("PGSSLCERT");
DBUrl += "&sslkey=/certs/client.root.pk8";
DBUrl += "&sslkey=/certs/client.root.key.pk8";
DBUrl += "&sslrootcert=/certs/ca.crt";
DBUrl += "&sslfactory=org.postgresql.ssl.jdbc4.LibPQFactory";
} else {
Expand Down
4 changes: 3 additions & 1 deletion pkg/cli/cert.go
Expand Up @@ -38,6 +38,7 @@ var caCertificateLifetime time.Duration
var certificateLifetime time.Duration
var allowCAKeyReuse bool
var overwriteFiles bool
var generatePKCS8Key bool

// A createCACert command generates a CA certificate and stores it
// in the cert directory.
Expand Down Expand Up @@ -187,7 +188,8 @@ func runCreateClientCert(cmd *cobra.Command, args []string) error {
keySize,
certificateLifetime,
overwriteFiles,
username),
username,
generatePKCS8Key),
"failed to generate client certificate and key")
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/cli/cliflags/flags.go
Expand Up @@ -471,6 +471,11 @@ a public network without combining it with --listen-addr.`,
Description: `Certificate and key files are overwritten if they exist.`,
}

GeneratePKCS8Key = FlagInfo{
Name: "also-generate-pkcs8-key",
Description: `Also write the key in pkcs8 format to <certs-dir>/client.<username>.key.pk8.`,
}

Password = FlagInfo{
Name: "password",
Description: `Prompt for the new user's password.`,
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/flags.go
Expand Up @@ -346,6 +346,8 @@ func init() {
IntFlag(f, &keySize, cliflags.KeySize, defaultKeySize)
BoolFlag(f, &overwriteFiles, cliflags.OverwriteFiles, false)
}
// PKCS8 key format is only available for the client cert command.
BoolFlag(createClientCertCmd.Flags(), &generatePKCS8Key, cliflags.GeneratePKCS8Key, false)

BoolFlag(setUserCmd.Flags(), &password, cliflags.Password, false)

Expand Down
25 changes: 24 additions & 1 deletion pkg/security/certs.go
Expand Up @@ -69,6 +69,15 @@ func writeKeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) e
return WritePEMToFile(keyFilePath, keyFileMode, overwrite, keyBlock)
}

func writePKCS8KeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) error {
keyBytes, err := PrivateKeyToPKCS8(key)
if err != nil {
return err
}

return SafeWriteToFile(keyFilePath, keyFileMode, overwrite, keyBytes)
}

// CreateCAPair creates a general CA certificate and associated key.
func CreateCAPair(
certsDir, caKeyPath string,
Expand Down Expand Up @@ -339,8 +348,14 @@ func CreateUIPair(
// The CA cert and key must load properly. If multiple certificates
// exist in the CA cert, the first one is used.
// If a client CA exists, this is used instead.
// If wantPKCS8Key is true, the private key in PKCS#8 encoding is written as well.
func CreateClientPair(
certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, user string,
certsDir, caKeyPath string,
keySize int,
lifetime time.Duration,
overwrite bool,
user string,
wantPKCS8Key bool,
) error {
if len(caKeyPath) == 0 {
return errors.New("the path to the CA key is required")
Expand Down Expand Up @@ -397,6 +412,14 @@ func CreateClientPair(
}
log.Infof(context.Background(), "Generated client key: %s", keyPath)

if wantPKCS8Key {
pkcs8KeyPath := keyPath + ".pk8"
if err := writePKCS8KeyToFile(pkcs8KeyPath, clientKey, overwrite); err != nil {
return errors.Errorf("error writing client PKCS8 key to %s: %v", pkcs8KeyPath, err)
}
log.Infof(context.Background(), "Generated PKCS8 client key: %s", pkcs8KeyPath)
}

return nil
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/security/certs_test.go
Expand Up @@ -170,7 +170,7 @@ func generateBaseCerts(certsDir string) error {

if err := security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedCAKey),
512, time.Hour*48, true, security.RootUser,
512, time.Hour*48, true, security.RootUser, false,
); err != nil {
return errors.Errorf("could not generate Client pair: %v", err)
}
Expand Down Expand Up @@ -208,14 +208,14 @@ func generateSplitCACerts(certsDir string) error {

if err := security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedClientCAKey),
512, time.Hour*48, true, security.NodeUser,
512, time.Hour*48, true, security.NodeUser, false,
); err != nil {
return errors.Errorf("could not generate Client pair: %v", err)
}

if err := security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedClientCAKey),
512, time.Hour*48, true, security.RootUser,
512, time.Hour*48, true, security.RootUser, false,
); err != nil {
return errors.Errorf("could not generate Client pair: %v", err)
}
Expand Down
31 changes: 30 additions & 1 deletion pkg/security/pem.go
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io"
"os"
"strings"

Expand All @@ -30,7 +31,7 @@ import (
// The file "path" is created with "mode" and WRONLY|CREATE.
// If overwrite is true, the file will be overwritten if it exists.
func WritePEMToFile(path string, mode os.FileMode, overwrite bool, blocks ...*pem.Block) error {
flags := os.O_WRONLY | os.O_CREATE
flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
if !overwrite {
flags |= os.O_EXCL
}
Expand All @@ -48,6 +49,29 @@ func WritePEMToFile(path string, mode os.FileMode, overwrite bool, blocks ...*pe
return f.Close()
}

// SafeWriteToFile writes the passed-in bytes to a file.
// The file "path" is created with "mode" and WRONLY|CREATE.
// If overwrite is true, the file will be overwritten if it exists.
func SafeWriteToFile(path string, mode os.FileMode, overwrite bool, contents []byte) error {
flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
if !overwrite {
flags |= os.O_EXCL
}
f, err := os.OpenFile(path, flags, mode)
if err != nil {
return err
}

n, err := f.Write(contents)
if err == nil && n < len(contents) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}

// PrivateKeyToPEM generates a PEM block from a private key.
func PrivateKeyToPEM(key crypto.PrivateKey) (*pem.Block, error) {
switch k := key.(type) {
Expand All @@ -64,6 +88,11 @@ func PrivateKeyToPEM(key crypto.PrivateKey) (*pem.Block, error) {
}
}

// PrivateKeyToPKCS8 encodes a private key into PKCS#8.
func PrivateKeyToPKCS8(key crypto.PrivateKey) ([]byte, error) {
return x509.MarshalPKCS8PrivateKey(key)
}

// PEMToCertificates parses multiple certificate PEM blocks and returns them.
// Each block must be a certificate.
// It is allowed to have zero certificates.
Expand Down