aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2025-04-11 12:40:54 -0400
committerSam Anthony <sam@samanthony.xyz>2025-04-11 12:40:54 -0400
commit93610fb8d1640763f0cd4ae5d465d8377696c273 (patch)
tree0fd87f6f0adf534303265bba768dc85bbe224c3c
parentb2695533495d5d467625ddaf67d5851c7bea1247 (diff)
downloadhose-93610fb8d1640763f0cd4ae5d465d8377696c273.zip
generate keypair
-rw-r--r--go.mod11
-rw-r--r--go.sum8
-rw-r--r--key/keygen.go99
3 files changed, 115 insertions, 3 deletions
diff --git a/go.mod b/go.mod
index 39a8912..cbd766d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,12 @@
module git.samanthony.xyz/hose
-go 1.22.0
+go 1.23.0
-toolchain go1.23.5
+toolchain go1.23.6
-require github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
+require (
+ github.com/adrg/xdg v0.5.3 // indirect
+ github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
+ golang.org/x/crypto v0.37.0 // indirect
+ golang.org/x/sys v0.32.0 // indirect
+)
diff --git a/go.sum b/go.sum
index cfa3b7d..a069c50 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,14 @@
code.cloudfoundry.org/bytefmt v0.29.0 h1:QaKuXtEY+gcSd1kXgdBN5U8h3mYmTvI2XyNh/5jEXXk=
code.cloudfoundry.org/bytefmt v0.29.0/go.mod h1:fVVUtTfimWCyT90RyJvmwZ0o8Q1d51RP8ByMvyceOXA=
+github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
+github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
diff --git a/key/keygen.go b/key/keygen.go
new file mode 100644
index 0000000..9857b76
--- /dev/null
+++ b/key/keygen.go
@@ -0,0 +1,99 @@
+package key
+
+import (
+ crypto_rand "crypto/rand"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/adrg/xdg"
+ "golang.org/x/crypto/nacl/box"
+)
+
+var (
+ pubKeyFile = filepath.Join(xdg.DataHome, "hose", "pubkey")
+ pubKeyFileMode os.FileMode = 0644
+
+ privKeyFile = filepath.Join(xdg.DataHome, "hose", "privkey")
+ privKeyFileMode os.FileMode = 0600
+)
+
+// Generate generates a new public/private keypair. It stores the private key in the
+// private key file and the public key in the public key file. If either of the key
+// files already exist, they will not be overwritten; instead an error will be returned.
+func Generate() error {
+ // Create public key file.
+ pubFile, err := createFile(pubKeyFile, pubKeyFileMode)
+ if err != nil {
+ return err
+ }
+ defer pubFile.Close()
+
+ // Create private key file.
+ privFile, err := createFile(privKeyFile, privKeyFileMode)
+ if err != nil {
+ pubFile.Close()
+ _ = os.Remove(pubKeyFile)
+ return err
+ }
+ defer privFile.Close()
+
+ // Generate keypair.
+ pubkey, privkey, err := box.GenerateKey(crypto_rand.Reader)
+ if err != nil {
+ return err
+ }
+
+ // Write keypair to files.
+ if _, err := pubFile.Write((*pubkey)[:]); err != nil {
+ return err
+ }
+ if _, err := privFile.Write((*privkey)[:]); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// createFile creates a file with the specified permissions and returns it for writing.
+// It does not truncate an existing file. If the file already exists, an error is returned.
+func createFile(name string, mode os.FileMode) (*os.File, error) {
+ exists, err := fileExists(name)
+ if err != nil {
+ return nil, err // unexpected error.
+ } else if exists {
+ return nil, errFileExists(name) // file exists; do not overwrite.
+ }
+ // Does not exist; continue;
+
+ f, err := os.Create(name)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := f.Chmod(mode); err != nil {
+ f.Close()
+ _ = os.Remove(name)
+ return nil, err
+ }
+
+ return f, nil
+}
+
+// fileExists returns a nil error and true/false if a file does/doesn't exist.
+// If an error is encountered, a non-nil error is returned.
+func fileExists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if errors.Is(err, os.ErrNotExist) {
+ return false, nil // file doesn't exist.
+ } else if err != nil {
+ return false, err // unexpected error.
+ }
+ return true, nil // file exists.
+}
+
+// errFileExists constructs a 'file already exists' error message.
+func errFileExists(path string) error {
+ return fmt.Errorf("%s: %s", os.ErrExist, path)
+}