safing-jess/envelope.go

351 lines
8.8 KiB
Go

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)
}