From 93610fb8d1640763f0cd4ae5d465d8377696c273 Mon Sep 17 00:00:00 2001 From: Sam Anthony Date: Fri, 11 Apr 2025 12:40:54 -0400 Subject: generate keypair --- go.mod | 11 +++++-- go.sum | 8 +++++ key/keygen.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 key/keygen.go 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) +} -- cgit v1.2.3