From 39cf202c708f5f86a4b7ecebbdddd0e38cd95f29 Mon Sep 17 00:00:00 2001 From: Soham S Gumaste Date: Wed, 28 May 2025 07:48:15 -0500 Subject: [PATCH 1/2] Add ability to generate a certificate from an existing key. --- cmd/nebula-cert/ca.go | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/cmd/nebula-cert/ca.go b/cmd/nebula-cert/ca.go index f83c94fb..ba037c73 100644 --- a/cmd/nebula-cert/ca.go +++ b/cmd/nebula-cert/ca.go @@ -13,6 +13,7 @@ import ( "strings" "time" + ed25519_std "crypto/ed25519" "github.com/skip2/go-qrcode" "github.com/slackhq/nebula/cert" "github.com/slackhq/nebula/pkclient" @@ -24,6 +25,7 @@ type caFlags struct { name *string duration *time.Duration outKeyPath *string + inKeyPath *string outCertPath *string outQRPath *string groups *string @@ -50,6 +52,7 @@ func newCaFlags() *caFlags { cf.version = cf.set.Uint("version", uint(cert.Version2), "Optional: version of the certificate format to use") cf.duration = cf.set.Duration("duration", time.Duration(time.Hour*8760), "Optional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"") cf.outKeyPath = cf.set.String("out-key", "ca.key", "Optional: path to write the private key to") + cf.inKeyPath = cf.set.String("in-key", "", "Optional: path to read private key for the new certificate") cf.outCertPath = cf.set.String("out-crt", "ca.crt", "Optional: path to write the certificate to") cf.outQRPath = cf.set.String("out-qr", "", "Optional: output a qr code image (png) of the certificate") cf.groups = cf.set.String("groups", "", "Optional: comma separated list of groups. This will limit which groups subordinate certs can use") @@ -95,6 +98,9 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error } if !isP11 { if err = mustFlagString("out-key", cf.outKeyPath); err != nil { + if err = mustFlagString("in-key", cf.outKeyPath); err != nil { + return err + } return err } } @@ -220,9 +226,25 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error switch *cf.curve { case "25519", "X25519", "Curve25519", "CURVE25519": curve = cert.Curve_CURVE25519 - pub, rawPriv, err = ed25519.GenerateKey(rand.Reader) - if err != nil { - return fmt.Errorf("error while generating ed25519 keys: %s", err) + if *cf.inKeyPath == "" { + pub, rawPriv, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return fmt.Errorf("error while generating ed25519 keys: %s", err) + } + } else { + var err error + rawPrivFile, err := os.ReadFile(*cf.inKeyPath) + if err != nil { + return err + } + rawPriv, _, _, err = cert.UnmarshalSigningPrivateKeyFromPEM(rawPrivFile) + + // type ed25519.PrivateKey + privEd25519 := ed25519_std.PrivateKey(rawPriv) + // .Public returns a crypto.PublicKey which is + // an alias for any + pub = privEd25519.Public().(ed25519.PublicKey) + } case "P256": var key *ecdsa.PrivateKey @@ -260,7 +282,10 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error if !isP11 { if _, err := os.Stat(*cf.outKeyPath); err == nil { - return fmt.Errorf("refusing to overwrite existing CA key: %s", *cf.outKeyPath) + // Check if we already have a private key + if *cf.inKeyPath == "" { + return fmt.Errorf("refusing to overwrite existing CA key: %s", *cf.outKeyPath) + } } } @@ -291,9 +316,12 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error b = cert.MarshalSigningPrivateKeyToPEM(curve, rawPriv) } - err = os.WriteFile(*cf.outKeyPath, b, 0600) - if err != nil { - return fmt.Errorf("error while writing out-key: %s", err) + // Do not write private key if one was provided + if *cf.inKeyPath != "" { + err = os.WriteFile(*cf.outKeyPath, b, 0600) + if err != nil { + return fmt.Errorf("error while writing out-key: %s", err) + } } } From cefc65fcf7395e474578c73e24f36d7cad67e524 Mon Sep 17 00:00:00 2001 From: Soham S Gumaste Date: Fri, 30 May 2025 15:22:38 -0500 Subject: [PATCH 2/2] Add ability to keygen public key from private key. --- cmd/nebula-cert/keygen.go | 26 ++++++++++++++++++++++---- cmd/nebula-cert/sign.go | 8 ++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/cmd/nebula-cert/keygen.go b/cmd/nebula-cert/keygen.go index 496f84c2..d9a929ed 100644 --- a/cmd/nebula-cert/keygen.go +++ b/cmd/nebula-cert/keygen.go @@ -15,6 +15,7 @@ type keygenFlags struct { set *flag.FlagSet outKeyPath *string outPubPath *string + inKeyPath *string curve *string p11url *string } @@ -24,6 +25,7 @@ func newKeygenFlags() *keygenFlags { cf.set.Usage = func() {} cf.outPubPath = cf.set.String("out-pub", "", "Required: path to write the public key to") cf.outKeyPath = cf.set.String("out-key", "", "Required: path to write the private key to") + cf.inKeyPath = cf.set.String("in-key", "", "Optional: Path to existing private key") cf.curve = cf.set.String("curve", "25519", "ECDH Curve (25519, P256)") cf.p11url = p11Flag(cf.set) return &cf @@ -40,7 +42,9 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error { if !isP11 { if err = mustFlagString("out-key", cf.outKeyPath); err != nil { - return err + if *cf.inKeyPath == "" { + return err + } } } if err = mustFlagString("out-pub", cf.outPubPath); err != nil { @@ -59,8 +63,22 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error { } else { switch *cf.curve { case "25519", "X25519", "Curve25519", "CURVE25519": - pub, rawPriv = x25519Keypair() - curve = cert.Curve_CURVE25519 + if *cf.inKeyPath == "" { + pub, rawPriv = x25519Keypair() + curve = cert.Curve_CURVE25519 + } else { + rawBytes, err := os.ReadFile(*cf.inKeyPath) + if err != nil { + return err + } + + rawPriv, _, _, err = cert.UnmarshalPrivateKeyFromPEM(rawBytes) + if err != nil { + return err + } + pub, _ = x25519KeyFromPriv(rawPriv) + + } case "P256": pub, rawPriv = p256Keypair() curve = cert.Curve_P256 @@ -81,7 +99,7 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error { if err != nil { return fmt.Errorf("error while getting public key: %w", err) } - } else { + } else if *cf.inKeyPath == "" { err = os.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600) if err != nil { return fmt.Errorf("error while writing out-key: %s", err) diff --git a/cmd/nebula-cert/sign.go b/cmd/nebula-cert/sign.go index ebcb592e..2a6699cf 100644 --- a/cmd/nebula-cert/sign.go +++ b/cmd/nebula-cert/sign.go @@ -404,6 +404,14 @@ func newKeypair(curve cert.Curve) ([]byte, []byte) { } } +func x25519KeyFromPriv(priv []byte) ([]byte, []byte) { + pubkey, err := curve25519.X25519(priv, curve25519.Basepoint) + if err != nil { + panic(err) + } + return pubkey, priv +} + func x25519Keypair() ([]byte, []byte) { privkey := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, privkey); err != nil {