From 6eb97bb780ea090599463914d8635a570ed5ee86 Mon Sep 17 00:00:00 2001 From: jmug Date: Tue, 20 Oct 2026 14:29:22 -0700 Subject: [PATCH] feat: Command that generates a private key with well known params. --- cmd/hmpki/main.go | 7 ++++ go.mod | 7 ++++ go.sum | 11 ++++++ internal/clis/hmpki/generate.go | 28 +++++++++++++++ internal/clis/hmpki/generate_key.go | 56 +++++++++++++++++++++++++++++ internal/clis/hmpki/rootcmd.go | 29 +++++++++++++++ pkg/pki/fileperms.go | 5 +++ 7 files changed, 143 insertions(+) create mode 100644 cmd/hmpki/main.go create mode 100644 go.sum create mode 100644 internal/clis/hmpki/generate.go create mode 100644 internal/clis/hmpki/generate_key.go create mode 100644 internal/clis/hmpki/rootcmd.go create mode 100644 pkg/pki/fileperms.go diff --git a/cmd/hmpki/main.go b/cmd/hmpki/main.go new file mode 100644 index 0000000..9183947 --- /dev/null +++ b/cmd/hmpki/main.go @@ -0,0 +1,7 @@ +package main + +import "git.handmadecities.com/HMC/shhh/internal/clis/hmpki" + +func main() { + hmpki.Execute() +} diff --git a/go.mod b/go.mod index 9f08730..4bf7a17 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module git.handmadecities.com/HMC/shhh go 1.27.2 + +require github.com/spf13/cobra v1.10.2 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ef5d78d --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/clis/hmpki/generate.go b/internal/clis/hmpki/generate.go new file mode 100644 index 0000000..4fc8a32 --- /dev/null +++ b/internal/clis/hmpki/generate.go @@ -0,0 +1,28 @@ +package hmpki + +import ( + "github.com/spf13/cobra" +) + +var Output string + +func init() { + rootCmd.AddCommand(generateCmd) + + // All "generate" commands output an artifact to a file, + // be it a key or a cert, etc. + generateCmd.PersistentFlags().StringVarP( + &Output, + "output", + "o", + "", + "output filename", + ) + generateCmd.MarkPersistentFlagRequired("output") + generateCmd.MarkPersistentFlagFilename("output") +} + +var generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate pki resources (keys/certs)", +} diff --git a/internal/clis/hmpki/generate_key.go b/internal/clis/hmpki/generate_key.go new file mode 100644 index 0000000..cb3c528 --- /dev/null +++ b/internal/clis/hmpki/generate_key.go @@ -0,0 +1,56 @@ +package hmpki + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "os" + + "git.handmadecities.com/HMC/shhh/pkg/pki" + "github.com/spf13/cobra" +) + +func init() { + generateCmd.AddCommand(generateKeyCmd) +} + +var generateKeyCmd = &cobra.Command{ + Use: "key", + Short: "Generate a private key and save it to a file", + RunE: func(cmd *cobra.Command, args []string) error { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + der, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return err + } + + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: der, + }) + + kf, err := os.OpenFile( + Output, + // Write only, and fail if it alreay exists. + os.O_WRONLY|os.O_CREATE|os.O_EXCL, + pki.PrivateKeyFilePerms, + ) + if err != nil { + return err + } + if _, err = kf.Write(pemBytes); err != nil { + return err + } + if err = kf.Close(); err != nil { + return err + } + + return nil + }, +} diff --git a/internal/clis/hmpki/rootcmd.go b/internal/clis/hmpki/rootcmd.go new file mode 100644 index 0000000..04de3ce --- /dev/null +++ b/internal/clis/hmpki/rootcmd.go @@ -0,0 +1,29 @@ +package hmpki + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// Get a root cobra command going, then code for generating: +// - Generate CA key +// - Generate CA cert +// - Generate server cert/key +// - Generate client cert/key + +var rootCmd = &cobra.Command{ + Use: "hmpki", + Short: "Generate keys/certs with set roles for mTLS auth between services", + // Run: func(cmd *cobra.Command, args []string) { + // fmt.Println("I'm a cobra command!") + // }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/pkg/pki/fileperms.go b/pkg/pki/fileperms.go new file mode 100644 index 0000000..d703751 --- /dev/null +++ b/pkg/pki/fileperms.go @@ -0,0 +1,5 @@ +package pki + +import "os" + +const PrivateKeyFilePerms os.FileMode = 0600