package main import ( "errors" "strings" "time" "github.com/AlecAivazis/survey/v2" "github.com/safing/jess" "github.com/safing/jess/tools" ) //nolint:gocognit func newSignet(name, scheme string, saveToTrustStore bool) (*jess.Signet, error) { // get name name = strings.TrimSpace(name) if name == "" { enterName := &survey.Input{ Message: "Enter name of signet:", } err := survey.AskOne(enterName, &name, survey.WithValidator(survey.MinLength(1))) if err != nil { return nil, err } } // get scheme scheme = strings.TrimSpace(scheme) if scheme == "" { // build selection list schemeSelection := [][]string{ {jess.SignetSchemePassword, "Password"}, {jess.SignetSchemeKey, "Key", "dynamic b/s (set manually via --symkeysize)"}, {"ECDH-X25519", "Receiving (KeyExchange)"}, {"Ed25519", "Signing"}, } // select scheme selectScheme := &survey.Select{ Message: "Select Tool/Scheme:", Options: formatColumns(schemeSelection), } err := survey.AskOne(selectScheme, &scheme, nil) if err != nil { return nil, err } scheme = strings.Fields(scheme)[0] } // generate key var signet *jess.Signet switch scheme { case jess.SignetSchemePassword: signet = &jess.Signet{ Version: 1, Scheme: jess.SignetSchemePassword, } case jess.SignetSchemeKey: signet = &jess.Signet{ Version: 1, Scheme: jess.SignetSchemeKey, } if defaultSymmetricKeySize == 0 { return nil, errors.New("missing key size, please supply with --symkeysize") } newKey, err := jess.RandomBytes(defaultSymmetricKeySize) if err != nil { return nil, err } signet.Key = newKey default: // get tool tool, err := tools.Get(scheme) if err != nil { return nil, err } // create base signet = jess.NewSignetBase(tool) // check if tool needs security level or manual key size if tool.Info.HasOption(tools.OptionNeedsSecurityLevel) && minimumSecurityLevel <= 0 { return nil, errors.New("missing security level, please supply with --seclevel") } if tool.Info.HasOption(tools.OptionNeedsDefaultKeySize) && defaultSymmetricKeySize <= 0 { return nil, errors.New("missing key size, please supply with --symkeysize") } // generate err = signet.GenerateKey() if err != nil { return nil, err } err = signet.StoreKey() if err != nil { return nil, err } } err := signet.AssignUUID() if err != nil { return nil, err } signet.Info = &jess.SignetInfo{ Name: name, Created: time.Now(), } if saveToTrustStore { // write signet err = trustStore.StoreSignet(signet) if err != nil { return nil, err } // export as recipient switch scheme { case jess.SignetSchemePassword, jess.SignetSchemeKey: // is secret, no recipient default: rcpt, err := signet.AsRecipient() if err != nil { return nil, err } err = rcpt.StoreKey() if err != nil { return nil, err } err = trustStore.StoreSignet(rcpt) if err != nil { return nil, err } } } return signet, nil } func selectSignets(envelope *jess.Envelope, scope string) error { // collect all signet schemes that fit the scope var schemes []string var promptMsg string var currentSignets []*jess.Signet filter := jess.FilterSignetOnly switch scope { case jess.SignetSchemePassword: schemes = []string{jess.SignetSchemePassword} promptMsg = "Select password references: (selection is AND, not OR!)" case jess.SignetSchemeKey: schemes = []string{jess.SignetSchemeKey} promptMsg = "Select keys: (selection is AND, not OR!)" case "pw/key": schemes = []string{jess.SignetSchemePassword, jess.SignetSchemeKey} promptMsg = "Select keys and password references: (selection is AND, not OR!)" currentSignets = envelope.Secrets case "recipient": //nolint:goconst promptMsg = "Select recipients: (selection is AND, not OR!)" for _, tool := range tools.AsList() { switch tool.Info.Purpose { case tools.PurposeKeyExchange, tools.PurposeKeyEncapsulation: schemes = append(schemes, tool.Info.Name) } } filter = jess.FilterRecipientOnly currentSignets = envelope.Recipients case "sender": promptMsg = "Select senders: (selection is AND, not OR!)" for _, tool := range tools.AsList() { if tool.Info.Purpose == tools.PurposeSigning { schemes = append(schemes, tool.Info.Name) } } currentSignets = envelope.Senders default: return errors.New("unknown signet selection scope") } // collect all signet for the scope's schemes signetCandidates, err := trustStore.SelectSignets(filter, schemes...) if err != nil { return err } if len(signetCandidates) == 0 { return errors.New("no signets available, please create some first using the generate command") } // select signets selectedSignets, err := pickSignet(signetCandidates, promptMsg, "", true, currentSignets) if err != nil { return err } // make stubs selectedSignetStubs := make([]*jess.Signet, 0, len(selectedSignets)) for _, signet := range selectedSignets { selectedSignetStubs = append(selectedSignetStubs, &jess.Signet{ ID: signet.ID, }) } // add signets to envelope switch scope { case "pw", "key", "pw/key": envelope.Secrets = selectedSignetStubs case "recipient": envelope.Recipients = selectedSignetStubs case "sender": envelope.Senders = selectedSignetStubs } return nil } func pickSignet(signetOptions []*jess.Signet, promptMsg, doneMsg string, multi bool, multiPreselected []*jess.Signet) ([]*jess.Signet, error) { // compile list signetSelection := make([][]string, 0, len(signetOptions)+1) var preSelected int if !multi && doneMsg != "" { signetSelection = append(signetSelection, []string{doneMsg}) } if multi { for _, signet := range multiPreselected { signetSelection = append(signetSelection, []string{ formatSignetName(signet), formatSignetType(signet), formatSignetScheme(signet), formatSignetPurpose(signet), formatSignetSecurityLevel(signet), signet.ID, }) preSelected++ } } signetOptionLoop: for _, signet := range signetOptions { // do not add pre-selected signets if multi { for _, preSelectedSignet := range multiPreselected { if signet.ID == preSelectedSignet.ID && signet.Public == preSelectedSignet.Public { continue signetOptionLoop } } } signetSelection = append(signetSelection, []string{ formatSignetName(signet), formatSignetType(signet), formatSignetScheme(signet), formatSignetPurpose(signet), formatSignetSecurityLevel(signet), signet.ID, }) } // select signet/s var selectedEntries []string if multi { formattedColumns := formatColumns(signetSelection) selectSignets := &survey.MultiSelect{ Message: promptMsg, Options: formattedColumns, Default: formattedColumns[:preSelected], PageSize: 15, } err := survey.AskOne(selectSignets, &selectedEntries, nil) if err != nil { return nil, err } } else { var selectedEnty string selectSignet := &survey.Select{ Message: promptMsg, Options: formatColumns(signetSelection), PageSize: 15, } err := survey.AskOne(selectSignet, &selectedEnty, nil) if err != nil { return nil, err } // check for done msg if strings.HasPrefix(selectedEnty, doneMsg+" ") { return nil, nil } selectedEntries = []string{selectedEnty} } // get selected signet/s var selectedSignets []*jess.Signet selectedEntriesLoop: for _, entry := range selectedEntries { fields := strings.Fields(entry) id := fields[len(fields)-1] // last entry if multi { for _, signet := range multiPreselected { if id == signet.ID { selectedSignets = append(selectedSignets, signet) continue selectedEntriesLoop } } } for _, signet := range signetOptions { if id == signet.ID { selectedSignets = append(selectedSignets, signet) continue selectedEntriesLoop } } } return selectedSignets, nil }