commit fcfe79244f893dcfeb87f1b6d09c1673f421b5e1 Author: jolheiser Date: Sun Sep 25 23:27:53 2022 -0500 initial commit Signed-off-by: jolheiser diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ec2045e --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 John Olheiser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..41e7f44 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# confage + +Config values using [age](https://age-encryption.org). + +## License + +[MIT](LICENSE) diff --git a/confage.go b/confage.go new file mode 100644 index 0000000..ff8aafa --- /dev/null +++ b/confage.go @@ -0,0 +1,104 @@ +package confage + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "strings" + + "filippo.io/age" +) + +// Type is a config type +type Type[T any] struct { + i age.Identity + r age.Recipient + Value T +} + +// MustNew returns a new Type, panicking on error +func MustNew[T any](key string, val T) Type[T] { + t, err := New(key, val) + if err != nil { + panic(err) + } + return t +} + +// New returns a new Type +func New[T any](key string, val T) (v Type[T], err error) { + v.Value = val + var i age.Identity + var r age.Recipient + switch { + case strings.HasPrefix(key, "AGE-SECRET-KEY-1"): + ii, err := age.ParseX25519Identity(key) + if err != nil { + return v, err + } + i = ii + r = ii.Recipient() + default: + i, err = age.NewScryptIdentity(key) + if err != nil { + return v, err + } + r, err = age.NewScryptRecipient(key) + if err != nil { + return v, err + } + } + v.i, v.r = i, r + return v, nil +} + +// String implements fmt.Stringer +// It simply returns MarshalText +func (t Type[T]) String() string { + b, err := t.MarshalText() + if err != nil { + return fmt.Sprintf("string error: %v", err) + } + return string(b) +} + +// MarshalText implements encoding.MarshalText +func (v Type[T]) MarshalText() ([]byte, error) { + var b bytes.Buffer + w, err := age.Encrypt(&b, v.r) + if err != nil { + return nil, err + } + if err := json.NewEncoder(w).Encode(v.Value); err != nil { + return nil, err + } + if err := w.Close(); err != nil { + return nil, err + } + out := base64.StdEncoding.EncodeToString(b.Bytes()) + return []byte(out), nil +} + +// UnmarshalText implements encoding.UnmarshalText +func (v *Type[T]) UnmarshalText(text []byte) error { + b, err := base64.StdEncoding.DecodeString(string(text)) + if err != nil { + return err + } + r, err := age.Decrypt(bytes.NewBuffer(b), v.i) + if err != nil { + return err + } + var out bytes.Buffer + if _, err := io.Copy(&out, r); err != nil { + return err + } + var val T + if err := json.Unmarshal(out.Bytes(), &val); err != nil { + return err + } + (*v).Value = val + return nil +} diff --git a/confage_test.go b/confage_test.go new file mode 100644 index 0000000..f78099e --- /dev/null +++ b/confage_test.go @@ -0,0 +1,37 @@ +package confage + +import ( + "encoding/json" + "testing" + + "github.com/matryer/is" +) + +func TestTypeScrypt(t *testing.T) { + assert := is.New(t) + + enc := MustNew("passphrase", 0) + enc.Value = 100 + payload, err := json.Marshal(enc) + assert.NoErr(err) // Should be able to marshal JSON + + dec := MustNew("passphrase", 0) + err = json.Unmarshal(payload, &dec) + assert.NoErr(err) // Should be able to unmarshal JSON + + assert.Equal(enc.Value, dec.Value) // Values should match +} + +func TestTypeX25519(t *testing.T) { + assert := is.New(t) + + enc := MustNew("AGE-SECRET-KEY-1F7SU7MXLVHU0SWQ3L2ZMW7G2NN2YTH88NLU6LVDHENTZLMCT3M5S799RDK", 100) + payload, err := json.Marshal(enc) + assert.NoErr(err) // Should be able to marshal JSON + + dec := MustNew("AGE-SECRET-KEY-1F7SU7MXLVHU0SWQ3L2ZMW7G2NN2YTH88NLU6LVDHENTZLMCT3M5S799RDK", 0) + err = json.Unmarshal(payload, &dec) + assert.NoErr(err) // Should be able to unmarshal JSON + + assert.Equal(enc.Value, dec.Value) // Values should match +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3ed5a85 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module go.jolheiser.com/confage + +go 1.19 + +require ( + filippo.io/age v1.0.0 + github.com/matryer/is v1.4.0 +) + +require ( + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6e3248a --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= +filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=