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
vendor
cmd/cmd
cmd/jess

View file

@ -1,9 +1,12 @@
/*
CLI:
### Jess 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 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
-o <file> ... write output to <file>
@ -23,11 +26,12 @@ jess show <file>
- letter
- seal (signature-only letter)
global arguments
--tsdir /path/to/truststore
--seclevel <uint>
--symkeysize <uint>
--quiet only output errors and warnings
jess generate
generate a new signet and store both signet and recipient in the truststore
*/
package main
global arguments
--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:",
Options: []string{
"Encrypt with password",
"Encrypt with keyfile",
"Encrypt for someone",
"Encrypt with key",
"Encrypt for someone and sign",
"Encrypt for someone but don't sign",
"Sign a file",
"Custom from scratch",
},
}
err := survey.AskOne(prompt, &preset, nil)
@ -39,26 +39,23 @@ func newEnvelope(name string) (*jess.Envelope, error) {
switch preset {
case "Encrypt with password":
envelope.Tools = jess.RecommendedStoragePassword
envelope.SuiteID = jess.SuitePassword
err = selectSignets(envelope, "pw")
case "Encrypt with keyfile":
envelope.Tools = jess.RecommendedStorageKey
case "Encrypt with key":
envelope.SuiteID = jess.SuiteKey
err = selectSignets(envelope, "key")
case "Encrypt for someone":
envelope.Tools = jess.RecommendedStorageKey
case "Encrypt for someone and sign":
envelope.SuiteID = jess.SuiteComplete
err = selectSignets(envelope, "recipient")
if err == nil {
err = selectSignets(envelope, "sender")
}
case "Encrypt for someone but don't sign":
envelope.SuiteID = jess.SuiteRcptOnly
err = selectSignets(envelope, "recipient")
case "Sign a file":
envelope.NoConfidentiality().NoIntegrity().NoRecipientAuth()
envelope.SuiteID = jess.SuiteSign
err = selectSignets(envelope, "sender")
case "Custom from scratch":
// do nothing
}
if err != nil {
return nil, err
@ -72,12 +69,13 @@ func editEnvelope(envelope *jess.Envelope) error {
// main menu
// print envelope status
envelope.SecurityLevel = 0 // reset
session, err := envelope.Correspondence(trustStore)
if err != nil {
fmt.Printf("Envelope status: %s\n", err)
} else {
fmt.Println("Envelope status: valid.")
envelope.MinimumSecurityLevel = session.SecurityLevel
envelope.SecurityLevel = session.SecurityLevel
}
// sub menu
@ -86,17 +84,18 @@ func editEnvelope(envelope *jess.Envelope) error {
Message: "Select to edit",
Options: formatColumns([][]string{
{"Done", "save and return"},
{" "},
{"Requirements", formatRequirements(envelope)},
{"Tools", strings.Join(envelope.Tools, ", ")},
{""},
{"Suite", envelope.SuiteID},
{"", "provides " + formatRequirements(envelope.Suite().Provides)},
{"", "and " + formatSecurityLevel(envelope.Suite().SecurityLevel)},
{"Secrets", formatSignetNames(envelope.Secrets)},
{"Recipients", formatSignetNames(envelope.Recipients)},
{"Senders", formatSignetNames(envelope.Senders)},
{" "},
{""},
{"Abort", "discard changes and return"},
{"Delete", "delete and return"},
}),
PageSize: 10,
PageSize: 15,
}
err = survey.AskOne(prompt, &submenu, nil)
if err != nil {
@ -111,10 +110,8 @@ func editEnvelope(envelope *jess.Envelope) error {
return nil
case strings.HasPrefix(submenu, "Delete"):
return trustStore.DeleteEnvelope(envelope.Name)
case strings.HasPrefix(submenu, "Requirements"):
err = editEnvelopeRequirements(envelope)
case strings.HasPrefix(submenu, "Tools"):
err = editEnvelopeTools(envelope)
case strings.HasPrefix(submenu, "Suite"):
err = editEnvelopeSuite(envelope)
case strings.HasPrefix(submenu, "Secrets"):
err = selectSignets(envelope, "pw/key")
case strings.HasPrefix(submenu, "Recipients"):
@ -128,69 +125,30 @@ func editEnvelope(envelope *jess.Envelope) error {
}
}
func editEnvelopeRequirements(envelope *jess.Envelope) error {
// TODO: improve
// get reqs
requirements := envelope.Requirements()
if requirements == nil {
return errors.New("envelope requirements uninitialized")
func editEnvelopeSuite(envelope *jess.Envelope) error {
all := jess.Suites()
suiteOptions := make([][]string, 0, len(all))
for _, suite := range all {
suiteOptions = append(suiteOptions, []string{
suite.ID,
"provides " + suite.Provides.ShortString(),
formatSecurityLevel(suite.SecurityLevel),
"uses " + strings.Join(suite.Tools, ", "),
formatSuiteStatus(suite),
})
}
// build defaults
var defaults []string
if requirements.Has(jess.Confidentiality) {
defaults = append(defaults, "Confidentiality")
var selectedSuite string
prompt := &survey.Select{
Message: "Select suite",
Options: formatColumns(suiteOptions),
PageSize: 10,
}
if requirements.Has(jess.Integrity) {
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)
err := survey.AskOne(prompt, &selectedSuite, nil)
if err != nil {
return err
}
// parse
requirements.Remove(jess.Confidentiality)
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
envelope.SuiteID = strings.Fields(selectedSuite)[0]
return envelope.ReloadSuite()
}

View file

@ -2,7 +2,6 @@ package main
import (
"errors"
"fmt"
"strings"
"time"
@ -32,19 +31,8 @@ func newSignet(name, scheme string) (*jess.Signet, error) {
schemeSelection := [][]string{
{jess.SignetSchemePassword, "Password"},
{jess.SignetSchemeKey, "Key", "dynamic b/s (set manually via --symkeysize)"},
}
for _, tool := range tools.AsList() {
// 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),
})
}
{"ECDH-X25519", "Receiving (KeyExchange)"},
{"Ed25519", "Signing"},
}
// select scheme
@ -204,42 +192,6 @@ func selectSignets(envelope *jess.Envelope, scope string) error {
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
selectedSignetStubs := make([]*jess.Signet, 0, len(selectedSignets))
for _, signet := range selectedSignets {

View file

@ -11,7 +11,7 @@ import (
"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
preSelectedTools := make([]string, 0, len(toolNames))
var preSelected int
@ -101,7 +101,7 @@ func pickTools(toolNames []string, promptMsg string) ([]string, error) {
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
for _, hashTool := range hashtools.AsList() {
if hashTool.SecurityLevel >= minSecurityLevel {
@ -126,7 +126,7 @@ func pickHashTool(prompt string, minSecurityLevel int) (string, error) {
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 {
if entry == s {
return true

View file

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

View file

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

View file

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

View file

@ -2,22 +2,42 @@ package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/safing/jess"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/tools"
)
func init() {
rootCmd.AddCommand(toolsCmd)
rootCmd.AddCommand(listCmd)
}
var toolsCmd = &cobra.Command{
Use: "tools",
Short: "list all available tools",
var listCmd = &cobra.Command{
Use: "list",
Short: "list all available suites and tools",
DisableFlagsInUseLine: true,
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{
{"Name/ID", "Purpose", "Security Level", "Author", "Comment"},
}
@ -34,7 +54,7 @@ var toolsCmd = &cobra.Command{
fmt.Println(line)
}
fmt.Printf("\nHashTools\n\n")
fmt.Printf("\n\nHashTools\n\n")
hashToolTable := [][]string{
{"Name/ID", "Security Level", "Author", "Comment"},
}

View file

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

View file

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

View file

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

View file

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