Skip to content

Commit

Permalink
Add JWT and SSH management to Rot
Browse files Browse the repository at this point in the history
go
- add cmdGenerateJWT for generating JWTs
- add cmdGenerateSSH for generating SSH certificates
- add cmdShowCertificate for viewing certificates
- add cmdShowJWT for viewing JWTs
- change most commands to move command details to their respective files

hugo
- add release notes
- change docs to reflect latest changes
  • Loading branch information
thequailman committed Mar 12, 2024
1 parent 7cff87e commit 8d1f167
Show file tree
Hide file tree
Showing 40 changed files with 1,555 additions and 849 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@
[:book: Docs](https://rotx.dev/docs/)\
[:motorway: Roadmap](https://github.com/orgs/candiddev/projects/6/views/31)

Rot is an open source command line (CLI) tool for managing secrets.
Rot is an open source command line (CLI) tool for managing cryptographic values.

Rot makes encrypting and decrypting secrets easy:
Rot makes cryptography easy:

- Generate keys and values using current best encryption
- Easily rekey secrets to the latest encryption standards
- Share your secrets with other users and devices
- One-way encryption for production secrets
- Run commands and scripts with secrets injected via environment variables
- Store your secrets securely in git with human-readable diffs
- Easily generate X.509 certificates and Certificate Authorities
- Rekey encrypted values to the latest encryption standards
- Share your encrypted values with other users and devices
- One-way encryption for zero-knowledge secrets
- Run commands and scripts with encrypted values injected via environment variables
- Store your encrypted values securely in git with human-readable diffs
- Generate and view X.509 certificates and Certificate Authorities
- Generate and view JWTs
- Generate SSH keys and certificates

Visit https://rotx.dev for more information.

Expand Down
163 changes: 87 additions & 76 deletions go/cmdAddKey.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,81 +12,92 @@ import (
"github.com/candiddev/shared/go/logger"
)

func cmdAddKey(ctx context.Context, args []string, f cli.Flags, c *cfg) errs.Err {
e := c.decryptPrivateKey(ctx)
if e != nil {
if e.Is(errNotInitialized) {
return cmdInit(ctx, args, f, c)
}

return e
func cmdAddKey() cli.Command[*cfg] {
return cli.Command[*cfg]{
ArgumentsRequired: []string{
"key name",
},
ArgumentsOptional: []string{
"public key, default: generate a PBKDF-protected asymmetric key",
},
Usage: "Add a new or existing User key to the configuration keys.",
Run: func(ctx context.Context, args []string, f cli.Flags, c *cfg) errs.Err {
e := c.decryptPrivateKey(ctx)
if e != nil {
if e.Is(errNotInitialized) {
return cmdInit().Run(ctx, args, f, c)
}

return e
}

var err error

var p string

var pub cryptolib.Key[cryptolib.KeyProviderPublic]

n := args[1]

if len(args) == 3 {
pub, err = cryptolib.ParseKey[cryptolib.KeyProviderPublic](args[2])
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}
} else {
var prv cryptolib.Key[cryptolib.KeyProviderPrivate]

prv, pub, err = cryptolib.NewKeysAsymmetric(c.Algorithms.Asymmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

prv.ID = n
pub.ID = n

v, err := cryptolib.KDFSet(cryptolib.Argon2ID, prv.ID, []byte(prv.String()), c.Algorithms.Symmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

if v.Ciphertext == "" {
p = prv.String()
} else {
p = v.String()
}

f, err := os.OpenFile(c.KeyPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(errors.New("error opening keys file"), err))
}

defer f.Close()

if _, err := f.WriteString(p + "\n"); err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(errors.New("error writing keys file"), err))
}
}

v, err := pub.Key.EncryptAsymmetric([]byte(c.privateKey.String()), pub.ID, c.Algorithms.Symmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

sig, err := cryptolib.NewSignature(c.privateKey, []byte(pub.String()))
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

v.KeyID = c.privateKey.ID

c.DecryptKeys[n] = cfgDecryptKey{
Modified: time.Now(),
PrivateKey: v,
PublicKey: pub,
Signature: sig,
}

return logger.Error(ctx, c.save(ctx))
},
}

var err error

var p string

var pub cryptolib.Key[cryptolib.KeyProviderPublic]

n := args[1]

if len(args) == 3 {
pub, err = cryptolib.ParseKey[cryptolib.KeyProviderPublic](args[2])
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}
} else {
var prv cryptolib.Key[cryptolib.KeyProviderPrivate]

prv, pub, err = cryptolib.NewKeysAsymmetric(c.Algorithms.Asymmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

prv.ID = n
pub.ID = n

v, err := cryptolib.KDFSet(cryptolib.Argon2ID, prv.ID, []byte(prv.String()), c.Algorithms.Symmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

if v.Ciphertext == "" {
p = prv.String()
} else {
p = v.String()
}

f, err := os.OpenFile(c.KeyPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(errors.New("error opening keys file"), err))
}

defer f.Close()

if _, err := f.WriteString(p + "\n"); err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(errors.New("error writing keys file"), err))
}
}

v, err := pub.Key.EncryptAsymmetric([]byte(c.privateKey.String()), pub.ID, c.Algorithms.Symmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

sig, err := cryptolib.NewSignature(c.privateKey, []byte(pub.String()))
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

v.KeyID = c.privateKey.ID

c.DecryptKeys[n] = cfgDecryptKey{
Modified: time.Now(),
PrivateKey: v,
PublicKey: pub,
Signature: sig,
}

return logger.Error(ctx, c.save(ctx))
}
36 changes: 22 additions & 14 deletions go/cmdAddPrivateKey.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,30 @@ import (
"github.com/candiddev/shared/go/logger"
)

func cmdAddPrivateKey(ctx context.Context, args []string, _ cli.Flags, c *cfg) errs.Err {
if c.PublicKey.IsNil() {
return logger.Error(ctx, errNotInitialized)
}
func cmdAddPrivateKey() cli.Command[*cfg] {
return cli.Command[*cfg]{
ArgumentsRequired: []string{
"name",
},
Usage: "Generate and add a cryptographic private key to the configuration values.",
Run: func(ctx context.Context, args []string, _ cli.Flags, c *cfg) errs.Err {
if c.PublicKey.IsNil() {
return logger.Error(ctx, errNotInitialized)
}

prv, pub, err := cryptolib.NewKeysAsymmetric(c.Algorithms.Asymmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}
prv, pub, err := cryptolib.NewKeysAsymmetric(c.Algorithms.Asymmetric)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

prv.ID = args[1]
pub.ID = args[1]
prv.ID = args[1]
pub.ID = args[1]

if err := c.encryptvalue(ctx, []byte(prv.String()), args[1], pub.String()); err != nil {
return logger.Error(ctx, err)
}
if err := c.encryptvalue(ctx, []byte(prv.String()), args[1], pub.String()); err != nil {
return logger.Error(ctx, err)
}

return logger.Error(ctx, c.save(ctx))
return logger.Error(ctx, c.save(ctx))
},
}
}
79 changes: 58 additions & 21 deletions go/cmdAddValue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,74 @@ package main

import (
"context"
"fmt"
"strconv"

"github.com/candiddev/shared/go/cli"
"github.com/candiddev/shared/go/errs"
"github.com/candiddev/shared/go/logger"
"github.com/candiddev/shared/go/types"
)

func cmdAddValue(ctx context.Context, args []string, f cli.Flags, c *cfg) errs.Err {
if c.PublicKey.IsNil() {
return logger.Error(ctx, errNotInitialized)
}
func cmdAddValue() cli.Command[*cfg] {
return cli.Command[*cfg]{
ArgumentsRequired: []string{
"name",
},
ArgumentsOptional: []string{
`comment, default: ""`,
},
Flags: cli.Flags{
"d": {
Default: []string{`\n`},
Usage: "Delimiter",
},
"l": {
Placeholder: "length",
Usage: "Generate a random string with this length instead of providing a value",
},
},
Usage: "Add a value to the configuration values.",
Run: func(ctx context.Context, args []string, f cli.Flags, c *cfg) errs.Err {
if c.PublicKey.IsNil() {
return logger.Error(ctx, errNotInitialized)
}

name := args[1]
delimiter := ""
comment := ""
name := args[1]
delimiter := ""
comment := ""

if len(args) >= 3 {
comment = args[2]
}
if len(args) >= 3 {
comment = args[2]
}

if v, ok := f.Value("d"); ok {
delimiter = v
}
if v, ok := f.Value("d"); ok {
delimiter = v
}

v, err := cli.Prompt("Value:", delimiter, true)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}
var b []byte

if err := c.encryptvalue(ctx, v, name, comment); err != nil {
return logger.Error(ctx, err)
}
var err error

return logger.Error(ctx, c.save(ctx))
if v, ok := f.Value("l"); ok {
l, err := strconv.Atoi(v)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(fmt.Errorf("error parsing length: %w", err)))
}

b = []byte(types.RandString(l))
} else {
b, err = cli.Prompt("Value:", delimiter, true)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}
}

if err := c.encryptvalue(ctx, b, name, comment); err != nil {
return logger.Error(ctx, err)
}

return logger.Error(ctx, c.save(ctx))
},
}
}
50 changes: 29 additions & 21 deletions go/cmdDecrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,34 @@ import (
"github.com/candiddev/shared/go/logger"
)

func cmdDecrypt(ctx context.Context, args []string, _ cli.Flags, c *cfg) errs.Err {
c.decryptKeysEncrypted(ctx)

value := args[1]

if value == "-" {
value = string(cli.ReadStdin())
}

ev, err := cryptolib.ParseEncryptedValue(value)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
func cmdDecrypt() cli.Command[*cfg] {
return cli.Command[*cfg]{
ArgumentsRequired: []string{
"value, or - for stdin",
},
Usage: "Decrypt a value and print it to stdout.",
Run: func(ctx context.Context, args []string, _ cli.Flags, c *cfg) errs.Err {
c.decryptKeysEncrypted(ctx)

value := args[1]

if value == "-" {
value = string(cli.ReadStdin())
}

ev, err := cryptolib.ParseEncryptedValue(value)
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

v, err := ev.Decrypt(c.keys.Keys())
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

logger.Raw(string(v))

return logger.Error(ctx, nil)
},
}

v, err := ev.Decrypt(c.keys.Keys())
if err != nil {
return logger.Error(ctx, errs.ErrReceiver.Wrap(err))
}

logger.Raw(string(v))

return logger.Error(ctx, nil)
}
Loading

0 comments on commit 8d1f167

Please sign in to comment.