315 lines
7.7 KiB
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
|
|
}
|