319 lines
8 KiB
Go
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)
|
|
}
|