safing-jess/cmd/cfg-signet.go

315 lines
7.7 KiB
Go

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
}