safing-jess/envelope.go
2020-01-13 00:15:58 +01:00

319 lines
8 KiB
Go

package jess
import (
"errors"
"fmt"
)
// Envelope holds configuration for jess to put data into a letter.
type Envelope struct { //nolint:maligned // TODO
Version uint8
Name string
Tools []string
// 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 distictions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security".
MinimumSecurityLevel int
No string
requirements *Requirements
// 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,
requirements: NewRequirements(),
}
e.SerializeRequirements()
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.LoadRequirements()
if err != nil {
return nil, err
}
if verifying {
// prep sender signets only
err = e.prepSignets(e.Senders, e.opening, 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
}
// NoRecipientAuth removes the requirement to authenticate the recipient.
func (e *Envelope) NoRecipientAuth() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(RecipientAuthentication)
return e
}
// NoSenderAuth removes the requirement to authenticate the sender.
func (e *Envelope) NoSenderAuth() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(SenderAuthentication)
e.SerializeRequirements()
return e
}
// NoConfidentiality removes the requirement to provide confidentiality.
func (e *Envelope) NoConfidentiality() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(Confidentiality)
e.SerializeRequirements()
return e
}
// NoIntegrity removes the requirement to provide integrity.
func (e *Envelope) NoIntegrity() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(Integrity)
e.SerializeRequirements()
return e
}
// Unsafe removes all requirements.
func (e *Envelope) Unsafe() *Envelope {
e.requirements = &Requirements{}
e.SerializeRequirements()
return e
}
// Requirements returns the required requirements.
func (e *Envelope) Requirements() *Requirements {
return e.requirements
}
// SetRequirements sets new requirements.
func (e *Envelope) SetRequirements(requirements *Requirements) {
e.requirements = requirements
}
// LoadRequirements loads the required requirements from the struct's exposed negated "No" specification.
func (e *Envelope) LoadRequirements() error {
if e.requirements == nil {
attrs, err := ParseRequirementsFromNoSpec(e.No)
if err != nil {
return nil
}
e.requirements = attrs
}
return nil
}
// SerializeRequirements saves the requirement requirements in the struct's exposed negated "No" specification.
func (e *Envelope) SerializeRequirements() {
e.No = e.requirements.SerializeToNoSpec()
}
// 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.MinimumSecurityLevel)
if err != nil {
return fmt.Errorf(`failed to get password for "%s": %s`, signet.ID, err)
}
continue
}
// 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
new, err := storage.GetSignet(signet.ID, recipients)
if err != nil {
return fmt.Errorf(`failed to get signet with ID "%s" from truststore: %s`, signet.ID, err)
}
// check for scheme mismatch
if signet.Scheme != "" && signet.Scheme != new.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, new.Scheme)
}
// apply signet back into envelope
signet = new
signets[i] = new
}
// 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
new, err := storage.GetSignet(signet.ID, false)
if err == nil && new.Info != nil {
if signet.Info == nil {
signet.Info = new.Info
} else {
signet.Info.Name = new.Info.Name
}
}
}
}
if createPassword {
return createPasswordCallback(signet, minSecurityLevel)
}
return getPasswordCallback(signet)
}