Update CLI to reflect change done in audit remediation

This commit is contained in:
Daniel 2020-01-24 16:06:27 +01:00
parent 31216b0885
commit d740d5fd91
13 changed files with 141 additions and 180 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
cpu.out cpu.out
vendor vendor
cmd/cmd
cmd/jess

View file

@ -1,9 +1,12 @@
/* ### Jess CLI
CLI:
This is currently still more of a planning and working document.
Here is the CLI interface that is planned:
```
jess create <envelope file> jess create <envelope file>
jess close <file> with <envelope file> jess close <file> with <envelope name>
encrypt a file, write to file with the same name, but with a .letter suffix encrypt a file, write to file with the same name, but with a .letter suffix
-o <file> ... write output to <file> -o <file> ... write output to <file>
@ -23,11 +26,12 @@ jess show <file>
- letter - letter
- seal (signature-only letter) - seal (signature-only letter)
global arguments jess generate
--tsdir /path/to/truststore generate a new signet and store both signet and recipient in the truststore
--seclevel <uint>
--symkeysize <uint>
--quiet only output errors and warnings
*/ global arguments
package main --tsdir /path/to/truststore
--seclevel <uint>
--symkeysize <uint>
--quiet only output errors and warnings
```

View file

@ -26,10 +26,10 @@ func newEnvelope(name string) (*jess.Envelope, error) {
Message: "Select preset:", Message: "Select preset:",
Options: []string{ Options: []string{
"Encrypt with password", "Encrypt with password",
"Encrypt with keyfile", "Encrypt with key",
"Encrypt for someone", "Encrypt for someone and sign",
"Encrypt for someone but don't sign",
"Sign a file", "Sign a file",
"Custom from scratch",
}, },
} }
err := survey.AskOne(prompt, &preset, nil) err := survey.AskOne(prompt, &preset, nil)
@ -39,26 +39,23 @@ func newEnvelope(name string) (*jess.Envelope, error) {
switch preset { switch preset {
case "Encrypt with password": case "Encrypt with password":
envelope.Tools = jess.RecommendedStoragePassword envelope.SuiteID = jess.SuitePassword
err = selectSignets(envelope, "pw") err = selectSignets(envelope, "pw")
case "Encrypt with key":
case "Encrypt with keyfile": envelope.SuiteID = jess.SuiteKey
envelope.Tools = jess.RecommendedStorageKey
err = selectSignets(envelope, "key") err = selectSignets(envelope, "key")
case "Encrypt for someone and sign":
case "Encrypt for someone": envelope.SuiteID = jess.SuiteComplete
envelope.Tools = jess.RecommendedStorageKey
err = selectSignets(envelope, "recipient") err = selectSignets(envelope, "recipient")
if err == nil { if err == nil {
err = selectSignets(envelope, "sender") err = selectSignets(envelope, "sender")
} }
case "Encrypt for someone but don't sign":
envelope.SuiteID = jess.SuiteRcptOnly
err = selectSignets(envelope, "recipient")
case "Sign a file": case "Sign a file":
envelope.NoConfidentiality().NoIntegrity().NoRecipientAuth() envelope.SuiteID = jess.SuiteSign
err = selectSignets(envelope, "sender") err = selectSignets(envelope, "sender")
case "Custom from scratch":
// do nothing
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -72,12 +69,13 @@ func editEnvelope(envelope *jess.Envelope) error {
// main menu // main menu
// print envelope status // print envelope status
envelope.SecurityLevel = 0 // reset
session, err := envelope.Correspondence(trustStore) session, err := envelope.Correspondence(trustStore)
if err != nil { if err != nil {
fmt.Printf("Envelope status: %s\n", err) fmt.Printf("Envelope status: %s\n", err)
} else { } else {
fmt.Println("Envelope status: valid.") fmt.Println("Envelope status: valid.")
envelope.MinimumSecurityLevel = session.SecurityLevel envelope.SecurityLevel = session.SecurityLevel
} }
// sub menu // sub menu
@ -86,17 +84,18 @@ func editEnvelope(envelope *jess.Envelope) error {
Message: "Select to edit", Message: "Select to edit",
Options: formatColumns([][]string{ Options: formatColumns([][]string{
{"Done", "save and return"}, {"Done", "save and return"},
{" "}, {""},
{"Requirements", formatRequirements(envelope)}, {"Suite", envelope.SuiteID},
{"Tools", strings.Join(envelope.Tools, ", ")}, {"", "provides " + formatRequirements(envelope.Suite().Provides)},
{"", "and " + formatSecurityLevel(envelope.Suite().SecurityLevel)},
{"Secrets", formatSignetNames(envelope.Secrets)}, {"Secrets", formatSignetNames(envelope.Secrets)},
{"Recipients", formatSignetNames(envelope.Recipients)}, {"Recipients", formatSignetNames(envelope.Recipients)},
{"Senders", formatSignetNames(envelope.Senders)}, {"Senders", formatSignetNames(envelope.Senders)},
{" "}, {""},
{"Abort", "discard changes and return"}, {"Abort", "discard changes and return"},
{"Delete", "delete and return"}, {"Delete", "delete and return"},
}), }),
PageSize: 10, PageSize: 15,
} }
err = survey.AskOne(prompt, &submenu, nil) err = survey.AskOne(prompt, &submenu, nil)
if err != nil { if err != nil {
@ -111,10 +110,8 @@ func editEnvelope(envelope *jess.Envelope) error {
return nil return nil
case strings.HasPrefix(submenu, "Delete"): case strings.HasPrefix(submenu, "Delete"):
return trustStore.DeleteEnvelope(envelope.Name) return trustStore.DeleteEnvelope(envelope.Name)
case strings.HasPrefix(submenu, "Requirements"): case strings.HasPrefix(submenu, "Suite"):
err = editEnvelopeRequirements(envelope) err = editEnvelopeSuite(envelope)
case strings.HasPrefix(submenu, "Tools"):
err = editEnvelopeTools(envelope)
case strings.HasPrefix(submenu, "Secrets"): case strings.HasPrefix(submenu, "Secrets"):
err = selectSignets(envelope, "pw/key") err = selectSignets(envelope, "pw/key")
case strings.HasPrefix(submenu, "Recipients"): case strings.HasPrefix(submenu, "Recipients"):
@ -128,69 +125,30 @@ func editEnvelope(envelope *jess.Envelope) error {
} }
} }
func editEnvelopeRequirements(envelope *jess.Envelope) error { func editEnvelopeSuite(envelope *jess.Envelope) error {
// TODO: improve all := jess.Suites()
suiteOptions := make([][]string, 0, len(all))
// get reqs for _, suite := range all {
requirements := envelope.Requirements() suiteOptions = append(suiteOptions, []string{
if requirements == nil { suite.ID,
return errors.New("envelope requirements uninitialized") "provides " + suite.Provides.ShortString(),
formatSecurityLevel(suite.SecurityLevel),
"uses " + strings.Join(suite.Tools, ", "),
formatSuiteStatus(suite),
})
} }
// build defaults var selectedSuite string
var defaults []string prompt := &survey.Select{
if requirements.Has(jess.Confidentiality) { Message: "Select suite",
defaults = append(defaults, "Confidentiality") Options: formatColumns(suiteOptions),
PageSize: 10,
} }
if requirements.Has(jess.Integrity) { err := survey.AskOne(prompt, &selectedSuite, nil)
defaults = append(defaults, "Integrity")
}
if requirements.Has(jess.RecipientAuthentication) {
defaults = append(defaults, "Recipient Authentication")
}
if requirements.Has(jess.SenderAuthentication) {
defaults = append(defaults, "Sender Authentication")
}
// prompt
var selected []string
prompt := &survey.MultiSelect{
Message: "Select requirements:",
Options: []string{
"Confidentiality",
"Integrity",
"Recipient Authentication",
"Sender Authentication",
},
Default: defaults,
}
err := survey.AskOne(prompt, &selected, nil)
if err != nil { if err != nil {
return err return err
} }
// parse envelope.SuiteID = strings.Fields(selectedSuite)[0]
requirements.Remove(jess.Confidentiality) return envelope.ReloadSuite()
requirements.Remove(jess.Integrity)
requirements.Remove(jess.RecipientAuthentication)
requirements.Remove(jess.SenderAuthentication)
for _, req := range selected {
switch req {
case "Confidentiality":
requirements.Add(jess.Confidentiality)
case "Integrity":
requirements.Add(jess.Integrity)
case "Recipient Authentication":
requirements.Add(jess.RecipientAuthentication)
case "Sender Authentication":
requirements.Add(jess.SenderAuthentication)
}
}
return nil
}
func editEnvelopeTools(envelope *jess.Envelope) (err error) {
envelope.Tools, err = pickTools(envelope.Tools, "Select tools:")
return err
} }

View file

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"time" "time"
@ -32,19 +31,8 @@ func newSignet(name, scheme string) (*jess.Signet, error) {
schemeSelection := [][]string{ schemeSelection := [][]string{
{jess.SignetSchemePassword, "Password"}, {jess.SignetSchemePassword, "Password"},
{jess.SignetSchemeKey, "Key", "dynamic b/s (set manually via --symkeysize)"}, {jess.SignetSchemeKey, "Key", "dynamic b/s (set manually via --symkeysize)"},
} {"ECDH-X25519", "Receiving (KeyExchange)"},
for _, tool := range tools.AsList() { {"Ed25519", "Signing"},
// check if tool support Signets
switch tool.Info.Purpose {
case tools.PurposeKeyExchange,
tools.PurposeKeyEncapsulation,
tools.PurposeSigning:
schemeSelection = append(schemeSelection, []string{
tool.Info.Name,
tool.Info.FormatPurpose(),
formatToolSecurityLevel(tool),
})
}
} }
// select scheme // select scheme
@ -204,42 +192,6 @@ func selectSignets(envelope *jess.Envelope, scope string) error {
return err return err
} }
// add schemes to envelope
checkSchemaLoop:
for _, signet := range selectedSignets {
switch signet.Scheme {
case jess.SignetSchemePassword, jess.SignetSchemeKey:
default:
for _, toolID := range envelope.Tools {
scheme := toolID
if strings.Contains(scheme, "(") {
scheme = strings.Split(scheme, "(")[0]
}
if scheme == signet.Scheme {
continue checkSchemaLoop
}
}
// schema not found in envelope toolset
tool, err := signet.Tool()
if err != nil {
return err
}
if tool.Info.HasOption(tools.OptionNeedsManagedHasher) ||
tool.Info.HasOption(tools.OptionNeedsDedicatedHasher) {
// add hash tool
hashToolName, err := pickHashTool(fmt.Sprintf("Select hash tool for %s:", tool.Info.Name), tool.Info.SecurityLevel)
if err != nil {
return err
}
envelope.Tools = append(envelope.Tools, fmt.Sprintf("%s(%s)", tool.Info.Name, hashToolName))
} else {
envelope.Tools = append(envelope.Tools, signet.Scheme)
}
}
}
// make stubs // make stubs
selectedSignetStubs := make([]*jess.Signet, 0, len(selectedSignets)) selectedSignetStubs := make([]*jess.Signet, 0, len(selectedSignets))
for _, signet := range selectedSignets { for _, signet := range selectedSignets {

View file

@ -11,7 +11,7 @@ import (
"github.com/AlecAivazis/survey" "github.com/AlecAivazis/survey"
) )
func pickTools(toolNames []string, promptMsg string) ([]string, error) { func pickTools(toolNames []string, promptMsg string) ([]string, error) { //nolint:unused,deadcode // TODO
var toolSelection [][]string //nolint:prealloc var toolSelection [][]string //nolint:prealloc
preSelectedTools := make([]string, 0, len(toolNames)) preSelectedTools := make([]string, 0, len(toolNames))
var preSelected int var preSelected int
@ -101,7 +101,7 @@ func pickTools(toolNames []string, promptMsg string) ([]string, error) {
return newTools, nil return newTools, nil
} }
func pickHashTool(prompt string, minSecurityLevel int) (string, error) { func pickHashTool(prompt string, minSecurityLevel int) (string, error) { //nolint:unused // TODO
var hashToolSelection [][]string var hashToolSelection [][]string
for _, hashTool := range hashtools.AsList() { for _, hashTool := range hashtools.AsList() {
if hashTool.SecurityLevel >= minSecurityLevel { if hashTool.SecurityLevel >= minSecurityLevel {
@ -126,7 +126,7 @@ func pickHashTool(prompt string, minSecurityLevel int) (string, error) {
return strings.Fields(selectedEnty)[0], nil return strings.Fields(selectedEnty)[0], nil
} }
func stringInSlice(s string, a []string) bool { func stringInSlice(s string, a []string) bool { //nolint:unused // TODO
for _, entry := range a { for _, entry := range a {
if entry == s { if entry == s {
return true return true

View file

@ -12,7 +12,7 @@ import (
func init() { func init() {
rootCmd.AddCommand(closeCmd) rootCmd.AddCommand(closeCmd)
closeCmd.Flags().StringVarP(&closeFlagOutput, "output", "o", "", "specify output file") closeCmd.Flags().StringVarP(&closeFlagOutput, "output", "o", "", "specify output file (`-` for stdout")
} }
var ( var (
@ -20,9 +20,11 @@ var (
closeCmdHelp = "usage: jess close <file> with <envelope name>" closeCmdHelp = "usage: jess close <file> with <envelope name>"
closeCmd = &cobra.Command{ closeCmd = &cobra.Command{
Use: "close", Use: "close <file> with <envelope name>",
Short: "encrypt file", Short: "encrypt file",
PreRunE: requireTrustStore, Long: "encrypt file with the given envelope. Use `-` to use stdin",
DisableFlagsInUseLine: true,
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
registerPasswordCallbacks() registerPasswordCallbacks()

View file

@ -16,10 +16,11 @@ func init() {
var ( var (
configureCmd = &cobra.Command{ configureCmd = &cobra.Command{
Use: "configure", Use: "configure <envelope name>",
Short: "configure (and create) envelope", Short: "configure (and create) envelope",
Args: cobra.MaximumNArgs(1), DisableFlagsInUseLine: true,
PreRunE: requireTrustStore, Args: cobra.MaximumNArgs(1),
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) (err error) { RunE: func(cmd *cobra.Command, args []string) (err error) {
// check envelope name existence // check envelope name existence
if len(args) == 0 { if len(args) == 0 {

View file

@ -15,10 +15,11 @@ var (
generateFlagScheme string generateFlagScheme string
generateCmd = &cobra.Command{ generateCmd = &cobra.Command{
Use: "generate", Use: "generate",
Short: "generate a new signet", Short: "generate a new signet",
Args: cobra.NoArgs, DisableFlagsInUseLine: true,
PreRunE: requireTrustStore, Args: cobra.NoArgs,
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
_, err := newSignet(generateFlagName, generateFlagScheme) _, err := newSignet(generateFlagName, generateFlagScheme)
return err return err

View file

@ -2,22 +2,42 @@ package main
import ( import (
"fmt" "fmt"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/safing/jess"
"github.com/safing/jess/hashtools" "github.com/safing/jess/hashtools"
"github.com/safing/jess/tools" "github.com/safing/jess/tools"
) )
func init() { func init() {
rootCmd.AddCommand(toolsCmd) rootCmd.AddCommand(listCmd)
} }
var toolsCmd = &cobra.Command{ var listCmd = &cobra.Command{
Use: "tools", Use: "list",
Short: "list all available tools", Short: "list all available suites and tools",
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Tools\n\n") fmt.Printf("Suites\n\n")
suitesTable := [][]string{
{"Name/ID", "Provides", "Security Level", "Tools", "Notes"},
}
for _, suite := range jess.Suites() {
suitesTable = append(suitesTable, []string{
suite.ID,
suite.Provides.ShortString(),
formatSecurityLevel(suite.SecurityLevel),
strings.Join(suite.Tools, ", "),
formatSuiteStatus(suite),
})
}
for _, line := range formatColumns(suitesTable) {
fmt.Println(line)
}
fmt.Printf("\n\nTools\n\n")
toolTable := [][]string{ toolTable := [][]string{
{"Name/ID", "Purpose", "Security Level", "Author", "Comment"}, {"Name/ID", "Purpose", "Security Level", "Author", "Comment"},
} }
@ -34,7 +54,7 @@ var toolsCmd = &cobra.Command{
fmt.Println(line) fmt.Println(line)
} }
fmt.Printf("\nHashTools\n\n") fmt.Printf("\n\nHashTools\n\n")
hashToolTable := [][]string{ hashToolTable := [][]string{
{"Name/ID", "Security Level", "Author", "Comment"}, {"Name/ID", "Security Level", "Author", "Comment"},
} }

View file

@ -19,10 +19,11 @@ func init() {
} }
var manageCmd = &cobra.Command{ var manageCmd = &cobra.Command{
Use: "manage", Use: "manage",
Short: "manage a trust store", Short: "manage a trust store",
Args: cobra.MaximumNArgs(1), DisableFlagsInUseLine: true,
PreRunE: requireTrustStore, Args: cobra.MaximumNArgs(1),
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// select action // select action
var selectedAction string var selectedAction string
@ -117,7 +118,11 @@ func manageEnvelopes() error {
for _, envelope := range all { for _, envelope := range all {
selection = append(selection, []string{ selection = append(selection, []string{
envelope.Name, envelope.Name,
envelope.Requirements().ShortString(), envelope.SuiteID,
fmt.Sprintf("provides %s and %s",
envelope.Suite().Provides.ShortString(),
formatSecurityLevel(envelope.Suite().SecurityLevel),
),
formatEnvelopeSignets(envelope), formatEnvelopeSignets(envelope),
}) })
} }

View file

@ -17,7 +17,7 @@ import (
func init() { func init() {
rootCmd.AddCommand(openCmd) rootCmd.AddCommand(openCmd)
openCmd.Flags().StringVarP(&openFlagOutput, "output", "o", "", "specify output file") openCmd.Flags().StringVarP(&openFlagOutput, "output", "o", "", "specify output file (`-` for stdout")
} }
var ( var (
@ -25,8 +25,9 @@ var (
openCmdHelp = "usage: jess open <file>" openCmdHelp = "usage: jess open <file>"
openCmd = &cobra.Command{ openCmd = &cobra.Command{
Use: "open", Use: "open <file>",
Short: "decrypt a file", Short: "decrypt file",
Long: "decrypt file with the given envelope. Use `-` to use stdin",
RunE: func(cmd *cobra.Command, args []string) (err error) { RunE: func(cmd *cobra.Command, args []string) (err error) {
registerPasswordCallbacks() registerPasswordCallbacks()
@ -39,7 +40,7 @@ var (
filename := args[0] filename := args[0]
outputFilename := openFlagOutput outputFilename := openFlagOutput
if outputFilename == "" { if outputFilename == "" {
if !strings.HasSuffix(filename, ".letter") || len(outputFilename) < 8 { if !strings.HasSuffix(filename, ".letter") || len(filename) < 8 {
return errors.New("cannot automatically derive output filename, please specify with --output") return errors.New("cannot automatically derive output filename, please specify with --output")
} }
outputFilename = strings.TrimSuffix(filename, ".letter") outputFilename = strings.TrimSuffix(filename, ".letter")

View file

@ -21,8 +21,9 @@ var (
verifyCmdHelp = "usage: jess verify <file>" verifyCmdHelp = "usage: jess verify <file>"
verifyCmd = &cobra.Command{ verifyCmd = &cobra.Command{
Use: "verify", Use: "verify <file>",
Short: "verify a file", Short: "verify file",
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) (err error) { RunE: func(cmd *cobra.Command, args []string) (err error) {
// check args // check args
if len(args) != 1 { if len(args) != 1 {

View file

@ -38,6 +38,10 @@ func formatColumns(table [][]string) []string {
return lines return lines
} }
func formatSecurityLevel(securityLevel int) string {
return fmt.Sprintf("%d b/s", securityLevel)
}
func formatToolSecurityLevel(tool *tools.Tool) string { func formatToolSecurityLevel(tool *tools.Tool) string {
if tool.Info.HasOption(tools.OptionNeedsSecurityLevel) { if tool.Info.HasOption(tools.OptionNeedsSecurityLevel) {
return "dynamic b/s (set manually via --seclevel)" return "dynamic b/s (set manually via --seclevel)"
@ -45,7 +49,7 @@ func formatToolSecurityLevel(tool *tools.Tool) string {
if tool.Info.SecurityLevel == 0 { if tool.Info.SecurityLevel == 0 {
return "" return ""
} }
return fmt.Sprintf("%d b/s", tool.Info.SecurityLevel) return formatSecurityLevel(tool.Info.SecurityLevel)
} }
func formatSignetName(signet *jess.Signet) string { func formatSignetName(signet *jess.Signet) string {
@ -116,12 +120,11 @@ func formatSignetSecurityLevel(signet *jess.Signet) string {
return fmt.Sprintf("%d b/s", securityLevel) return fmt.Sprintf("%d b/s", securityLevel)
} }
func formatRequirements(envelope *jess.Envelope) string { func formatRequirements(reqs *jess.Requirements) string {
attrs := envelope.Requirements() if reqs == nil || reqs.Empty() {
if attrs == nil || attrs.Empty() {
return "none (unsafe)" return "none (unsafe)"
} }
return attrs.String() return reqs.String()
} }
func formatSignetNames(signets []*jess.Signet) string { func formatSignetNames(signets []*jess.Signet) string {
@ -145,3 +148,14 @@ func formatEnvelopeSignets(envelope *jess.Envelope) string {
} }
return strings.Join(sections, ", ") return strings.Join(sections, ", ")
} }
func formatSuiteStatus(suite *jess.Suite) string {
switch suite.Status {
case jess.SuiteStatusDeprecated:
return "DEPRECATED"
case jess.SuiteStatusRecommended:
return "recommended"
default:
return ""
}
}