Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions cmd/nebula-cert/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,6 +25,7 @@ type caFlags struct {
name *string
duration *time.Duration
outKeyPath *string
inKeyPath *string
outCertPath *string
outQRPath *string
groups *string
Expand All @@ -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")
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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 == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really like to have this feature, but I think if we're going to add it, we need to support P256 as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it. My particular use case was Curve25519 but I don't see too much trouble adding this for P256 too.

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
Expand Down Expand Up @@ -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)
}
}
}

Expand Down Expand Up @@ -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)
}
}
}

Expand Down
26 changes: 22 additions & 4 deletions cmd/nebula-cert/keygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type keygenFlags struct {
set *flag.FlagSet
outKeyPath *string
outPubPath *string
inKeyPath *string
curve *string
p11url *string
}
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this ought to be it's own "command"? Making this a bonus feature of keygen is potentially confusing.

Also, many users encrypt their private keys, we may need to support that. I think there's a helper function for this.... somewhere?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I did spot the helper, I threw this together as a Proof-Of-Concept and without knowing if this feature was desired. I'll add encrypted keys and a new subcommand.

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
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions cmd/nebula-cert/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most cases, I prefer to bubble the error back up and let the caller decide to panic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the original function I modeled this one from (similar name in the same file) didn't bubble err, so I didn't do it either. That being said its a relatively minor fix, which I will implement.

}
return pubkey, priv
}

func x25519Keypair() ([]byte, []byte) {
privkey := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, privkey); err != nil {
Expand Down