263 lines
6.9 KiB
Go
263 lines
6.9 KiB
Go
// Container versions
|
|
//
|
|
// 1: for network, simple
|
|
// 2: for storage
|
|
// 3: for network, concealed (TBD)
|
|
|
|
package jess
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/safing/structures/container"
|
|
"github.com/safing/structures/dsd"
|
|
)
|
|
|
|
// Letter is the data format for encrypted data at rest or in transit.
|
|
type Letter struct { //nolint:maligned // TODO
|
|
Version uint8 // signed, MAC'd (may not exist when wired)
|
|
SuiteID string // signed, MAC'd (may not exist when wired)
|
|
|
|
Nonce []byte // signed, MAC'd
|
|
Keys []*Seal `json:",omitempty"` // signed, MAC'd
|
|
|
|
Data []byte `json:",omitempty"` // signed, MAC'd
|
|
Mac []byte `json:",omitempty"` // signed
|
|
Signatures []*Seal `json:",omitempty"`
|
|
|
|
// Flags for wire protocol
|
|
ApplyKeys bool `json:",omitempty"` // MAC'd
|
|
}
|
|
|
|
// Seal holds a key, key exchange or signature within a letter.
|
|
type Seal struct {
|
|
Scheme string `json:",omitempty"`
|
|
|
|
// Key Establishment: Signet ID of recipient's signet
|
|
// Signature: Signet ID of signer's signet
|
|
ID string `json:",omitempty"`
|
|
|
|
// Key Establishment: Public key or wrapped key
|
|
// Signature: Signature value
|
|
Value []byte `json:",omitempty"`
|
|
}
|
|
|
|
// Envelope returns an envelope built from the letter, configured for opening it.
|
|
func (letter *Letter) Envelope(requirements *Requirements) (*Envelope, error) {
|
|
// basic checks
|
|
if letter.Version == 0 {
|
|
return nil, fmt.Errorf("letter does not specify version")
|
|
}
|
|
if len(letter.SuiteID) == 0 {
|
|
return nil, fmt.Errorf("letter does not specify a suite")
|
|
}
|
|
|
|
// create envelope
|
|
e := &Envelope{
|
|
Version: letter.Version,
|
|
SuiteID: letter.SuiteID,
|
|
}
|
|
|
|
// get and check suite
|
|
err := e.LoadSuite()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// default to full requirements
|
|
if requirements == nil {
|
|
requirements = NewRequirements()
|
|
}
|
|
// check suite against requirements
|
|
err = e.suite.Provides.CheckComplianceTo(requirements)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, seal := range letter.Keys {
|
|
// handshake messages have ephermal encapsulation keys in first message
|
|
if len(seal.ID) > 0 {
|
|
if seal.Scheme == SignetSchemeKey || seal.Scheme == SignetSchemePassword {
|
|
e.Secrets = append(e.Secrets, &Signet{
|
|
Version: letter.Version,
|
|
ID: seal.ID,
|
|
Scheme: seal.Scheme,
|
|
})
|
|
} else {
|
|
e.Recipients = append(e.Recipients, &Signet{
|
|
Version: letter.Version,
|
|
ID: seal.ID,
|
|
Scheme: seal.Scheme,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
for _, seal := range letter.Signatures {
|
|
e.Senders = append(e.Senders, &Signet{
|
|
Version: letter.Version,
|
|
ID: seal.ID,
|
|
Scheme: seal.Scheme,
|
|
})
|
|
}
|
|
|
|
e.opening = true
|
|
return e, nil
|
|
}
|
|
|
|
// Open creates a session and opens the letter in one step.
|
|
func (letter *Letter) Open(requirements *Requirements, trustStore TrustStore) ([]byte, error) {
|
|
e, err := letter.Envelope(requirements)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s, err := e.Correspondence(trustStore)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.Open(letter)
|
|
}
|
|
|
|
// Verify creates a session and verifies the letter in one step.
|
|
func (letter *Letter) Verify(requirements *Requirements, trustStore TrustStore) error {
|
|
e, err := letter.Envelope(requirements)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s, err := e.initCorrespondence(trustStore, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.Verify(letter)
|
|
}
|
|
|
|
// WireCorrespondence creates a wire session (communication over a network connection) from a letter.
|
|
func (letter *Letter) WireCorrespondence(trustStore TrustStore) (*Session, error) {
|
|
e, err := letter.Envelope(NewRequirements().Remove(SenderAuthentication))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return e.WireCorrespondence(trustStore)
|
|
}
|
|
|
|
// ToJSON serializes the letter to json.
|
|
func (letter *Letter) ToJSON() ([]byte, error) {
|
|
return json.Marshal(letter)
|
|
}
|
|
|
|
// LetterFromJSON loads a json-serialized letter.
|
|
func LetterFromJSON(data []byte) (*Letter, error) {
|
|
letter := &Letter{}
|
|
|
|
err := json.Unmarshal(data, letter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return letter, nil
|
|
}
|
|
|
|
// ToDSD serializes the letter to the given dsd format.
|
|
func (letter *Letter) ToDSD(dsdFormat uint8) ([]byte, error) {
|
|
data, err := dsd.Dump(letter, dsdFormat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// LetterFromDSD loads a dsd-serialized letter.
|
|
func LetterFromDSD(data []byte) (*Letter, error) {
|
|
letter := &Letter{}
|
|
|
|
_, err := dsd.Load(data, letter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return letter, nil
|
|
}
|
|
|
|
const (
|
|
// Field IDs for signing
|
|
// These IDs MUST NOT CHANGE.
|
|
|
|
fieldIDLetterVersion uint64 = 1 // signed, MAC'd (may not exist when wired)
|
|
fieldIDLetterSuiteID uint64 = 2 // signed, MAC'd (may not exist when wired)
|
|
fieldIDLetterNonce uint64 = 3 // signed, MAC'd
|
|
fieldIDLetterKeys uint64 = 4 // signed, MAC'd
|
|
fieldIDLetterMac uint64 = 5 // signed
|
|
|
|
fieldIDSealScheme uint64 = 16 // signed, MAC'd
|
|
fieldIDSealID uint64 = 17 // signed, MAC'd
|
|
fieldIDSealValue uint64 = 18 // signed, MAC'd
|
|
)
|
|
|
|
func (letter *Letter) compileAssociatedData() []byte {
|
|
// every field is transformed and prepended with a static ID
|
|
// this makes it easy to stay backward compatible without hassling around with versioning when fields are added
|
|
|
|
c := container.New()
|
|
|
|
if letter.Version > 0 {
|
|
c.AppendNumber(fieldIDLetterVersion) // append field ID
|
|
c.AppendNumber(uint64(letter.Version))
|
|
}
|
|
if len(letter.SuiteID) > 0 {
|
|
c.AppendNumber(fieldIDLetterSuiteID) // append field ID
|
|
c.AppendAsBlock([]byte(letter.SuiteID)) // append field content with length
|
|
}
|
|
if len(letter.Nonce) > 0 {
|
|
c.AppendNumber(fieldIDLetterNonce) // append field ID
|
|
c.AppendAsBlock(letter.Nonce) // append field content with length
|
|
}
|
|
if len(letter.Keys) > 0 {
|
|
c.AppendNumber(fieldIDLetterKeys) // append field ID
|
|
c.AppendInt(len(letter.Keys)) // append number of keys
|
|
for i, seal := range letter.Keys {
|
|
c.AppendInt(i) // append index
|
|
seal.compileAssociatedData(c) // append field content with length
|
|
}
|
|
}
|
|
|
|
return c.CompileData()
|
|
}
|
|
|
|
func (letter *Letter) compileAssociatedSigningData(associatedData []byte) []byte {
|
|
// compile basic associated data if not yet done
|
|
if len(associatedData) == 0 {
|
|
associatedData = letter.compileAssociatedData()
|
|
}
|
|
|
|
// return if there is no Mac
|
|
if len(letter.Mac) == 0 {
|
|
return associatedData
|
|
}
|
|
|
|
// add Mac to associated data and return
|
|
c := container.New(associatedData)
|
|
c.AppendNumber(fieldIDLetterMac) // append field ID
|
|
c.AppendAsBlock(letter.Mac) // append field content with length
|
|
|
|
return c.CompileData()
|
|
}
|
|
|
|
func (seal *Seal) compileAssociatedData(c *container.Container) {
|
|
if seal.Scheme != "" {
|
|
c.AppendNumber(fieldIDSealScheme) // append field ID
|
|
c.AppendAsBlock([]byte(seal.Scheme)) // append field content with length
|
|
}
|
|
if seal.ID != "" {
|
|
c.AppendNumber(fieldIDSealID) // append field ID
|
|
c.AppendAsBlock([]byte(seal.ID)) // append field content with length
|
|
}
|
|
if len(seal.Value) > 0 {
|
|
c.AppendNumber(fieldIDSealValue) // append field ID
|
|
c.AppendAsBlock(seal.Value) // append field content with length
|
|
}
|
|
}
|