package jess import ( "errors" "fmt" "github.com/mr-tron/base58" "github.com/safing/structures/dsd" ) // Envelope holds configuration for jess to put data into a letter. type Envelope struct { //nolint:maligned // TODO Version uint8 Name string SuiteID string suite *Suite // Secret keys and passwords Secrets []*Signet // Sender related signets // When closing: private keys for signatures // When opening: public keys for signatures Senders []*Signet // Recipient related signets // When closing: public keys for key exchange or key encapsulation // When opening: private keys for key exchange or key encapsulation Recipients []*Signet // For users, envelopes describe how a letter is closed. // Therefore Secrets and Senders always refer to private keys and Recipients to public keys in that context. // These distinctions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security". // SecurityLevel is the security level of the envelope when it was created SecurityLevel int // flag to signify if envelope is used for opening opening bool } // NewUnconfiguredEnvelope returns an unconfigured, but slightly initialized envelope. func NewUnconfiguredEnvelope() *Envelope { e := &Envelope{ Version: 1, } return e } // Correspondence returns a new session configured with the envelope. func (e *Envelope) Correspondence(trustStore TrustStore) (*Session, error) { return e.initCorrespondence(trustStore, false) } func (e *Envelope) initCorrespondence(trustStore TrustStore, verifying bool) (*Session, error) { err := e.LoadSuite() if err != nil { return nil, err } //nolint:gocritic // TODO: see below if verifying { // TODO: prep sender signets only // TODO: for this to work, newSession needs to only check verification related things // err = e.prepSignets(e.Senders, e.opening, trustStore) err = e.PrepareSignets(trustStore) } else { // prep all signets err = e.PrepareSignets(trustStore) } if err != nil { return nil, err } return newSession(e) } // WireCorrespondence returns a new wire session (live communication) configured with the envelope. func (e *Envelope) WireCorrespondence(trustStore TrustStore) (*Session, error) { s, err := e.Correspondence(trustStore) if err != nil { return nil, err } err = s.initWireSession() if err != nil { return nil, err } return s, nil } // Check returns whether the envelope is valid and can be used as is. func (e *Envelope) Check(trustStore TrustStore) error { _, err := e.Correspondence(trustStore) return err } // Suite returns the loaded suite. func (e *Envelope) Suite() *Suite { return e.suite } // LoadSuite loads the suite specified in the envelope. func (e *Envelope) LoadSuite() error { if e.suite == nil { suite, ok := GetSuite(e.SuiteID) if !ok { return fmt.Errorf("suite %s does not exist", e.SuiteID) } e.suite = suite } return nil } // ReloadSuite forces reloading the suite specified in the envelope. func (e *Envelope) ReloadSuite() error { e.suite = nil return e.LoadSuite() } // LoopSecrets loops over all secrets of the given scheme. func (e *Envelope) LoopSecrets(scheme string, fn func(*Signet) error) error { for _, signet := range e.Secrets { if len(scheme) == 0 || signet.Scheme == scheme { err := fn(signet) if err != nil { return err } } } return nil } // LoopSenders loops over all sender signets of the given scheme. func (e *Envelope) LoopSenders(scheme string, fn func(*Signet) error) error { for _, signet := range e.Senders { if len(scheme) == 0 || signet.Scheme == scheme { err := fn(signet) if err != nil { return err } } } return nil } // LoopRecipients loops over all recipient signets of the given scheme. func (e *Envelope) LoopRecipients(scheme string, fn func(*Signet) error) error { for _, signet := range e.Recipients { if len(scheme) == 0 || signet.Scheme == scheme { err := fn(signet) if err != nil { return err } } } return nil } // PrepareSignets checks that all signets of the envelope are ready to use. It will fetch referenced signets and load the keys. func (e *Envelope) PrepareSignets(storage TrustStore) error { err := e.prepSignets(e.Secrets, e.opening, storage) if err != nil { return err } err = e.prepSignets(e.Senders, e.opening, storage) if err != nil { return err } return e.prepSignets(e.Recipients, !e.opening, storage) } // prepSignets checks that all signets of the envelope are ready to use. func (e *Envelope) prepSignets(signets []*Signet, recipients bool, storage TrustStore) error { for i, signet := range signets { // load from storage if len(signet.Key) == 0 { if signet.Scheme == SignetSchemePassword { err := fillPassword(signet, !recipients, storage, e.suite.SecurityLevel) if err != nil { return fmt.Errorf(`failed to get password for "%s": %w`, signet.ID, err) } continue } // keys are _always_ signets if signet.Scheme == SignetSchemeKey { recipients = false // TODO: spills to next loop } // signet is referrer if len(signet.ID) == 0 { return errors.New("signets must have a scheme+key or an ID") } // check if we have a storage if storage == nil { return fmt.Errorf(`failed to get signet with ID "%s": no truststore provided`, signet.ID) } // get signet from trust store newSignet, err := storage.GetSignet(signet.ID, recipients) if err != nil { return fmt.Errorf(`failed to get signet with ID "%s" from truststore: %w`, signet.ID, err) } // check for scheme mismatch if signet.Scheme != "" && signet.Scheme != newSignet.Scheme { return fmt.Errorf(`failed to apply signet with ID "%s" from truststore: was expected to be of type %s, but is %s`, signet.ID, signet.Scheme, newSignet.Scheme) } // apply signet back into envelope signet = newSignet signets[i] = newSignet } // unwrap protection if signet.Protection != nil { return errors.New("protected signets are not yet supported") } // load signet switch signet.Scheme { case SignetSchemeKey, SignetSchemePassword: // no loading needed default: err := signet.LoadKey() if err != nil { return err } } } return nil } func fillPassword(signet *Signet, createPassword bool, storage TrustStore, minSecurityLevel int) (err error) { if createPassword { if createPasswordCallback == nil { return nil // ignore } } else if getPasswordCallback == nil { return nil } // find reference if signet.Info == nil || signet.Info.Name == "" { // check trust store for name if len(signet.ID) > 0 && storage != nil { // get signet from trust store newSignet, err := storage.GetSignet(signet.ID, false) if err == nil && newSignet.Info != nil { if signet.Info == nil { signet.Info = newSignet.Info } else { signet.Info.Name = newSignet.Info.Name } } } } if createPassword { return createPasswordCallback(signet, minSecurityLevel) } return getPasswordCallback(signet) } // CleanSignets cleans all the signets from all the non-necessary data as well // as key material. // This is for preparing for serializing and saving the signet. func (e *Envelope) CleanSignets() { for i, signet := range e.Secrets { e.Secrets[i] = &Signet{ Version: signet.Version, ID: signet.ID, Scheme: signet.Scheme, } } for i, signet := range e.Senders { e.Senders[i] = &Signet{ Version: signet.Version, ID: signet.ID, Scheme: signet.Scheme, } } for i, signet := range e.Recipients { e.Recipients[i] = &Signet{ Version: signet.Version, ID: signet.ID, Scheme: signet.Scheme, } } } // ToBytes serializes the envelope to a byte slice. func (e *Envelope) ToBytes() ([]byte, error) { // Minimize data and remove any key material. e.CleanSignets() // Serialize envelope. data, err := dsd.Dump(e, dsd.CBOR) if err != nil { return nil, fmt.Errorf("failed to serialize the envelope: %w", err) } return data, nil } // EnvelopeFromBytes parses and loads a serialized envelope. func EnvelopeFromBytes(data []byte) (*Envelope, error) { e := &Envelope{} // Parse envelope from data. if _, err := dsd.Load(data, e); err != nil { return nil, fmt.Errorf("failed to parse data format: %w", err) } return e, nil } // ToBase58 serializes the envelope and encodes it with base58. func (e *Envelope) ToBase58() (string, error) { // Serialize Signet. data, err := e.ToBytes() if err != nil { return "", err } // Encode and return. return base58.Encode(data), nil } // EnvelopeFromBase58 parses and loads a base58 encoded serialized envelope. func EnvelopeFromBase58(base58Encoded string) (*Envelope, error) { // Decode string. data, err := base58.Decode(base58Encoded) if err != nil { return nil, fmt.Errorf("failed to decode base58: %w", err) } // Parse and return. return EnvelopeFromBytes(data) }