Skip to content

Commit

Permalink
Envelope Trees and Bug Fixes (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort authored Jul 22, 2024
1 parent 226a4a2 commit 9157aba
Show file tree
Hide file tree
Showing 6 changed files with 611 additions and 290 deletions.
226 changes: 95 additions & 131 deletions pkg/trisa/envelope/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,128 +55,6 @@ import (
"google.golang.org/protobuf/proto"
)

// Seal an envelope using the public signing key of the TRISA peer (must be supplied via
// the WithSealingKey or WithRSAPublicKey options). A secure envelope is created by
// marshaling the payload, encrypting it, then sealing the envelope by encrypting the
// encryption key and hmac secret with the public key of the recipient. This method
// returns two types of errors: a rejection error can be returned to the sender to
// indicate that the TRISA protocol failed, otherwise an error is returned for the user
// to handle. This method is a convenience one-liner, for more control of the sealing
// process or to manage intermediate steps, use the Envelope wrapper directly.
func Seal(payload *api.Payload, opts ...Option) (_ *api.SecureEnvelope, reject *api.Error, err error) {
var env *Envelope
if env, err = New(payload, opts...); err != nil {
return nil, nil, err
}

// Validate the payload before encrypting
if err = env.ValidatePayload(); err != nil {
return nil, nil, err
}

// Create a new AES-GCM crypto handler if one is not supplied on the envelope. This
// generates a random encryption key and hmac secret on a per-envelope basis,
// helping to prevent statistical cryptographic attacks.
if env.crypto == nil {
if env.crypto, err = aesgcm.New(nil, nil); err != nil {
return nil, nil, err
}
}

if reject, err = env.encrypt(payload); reject != nil || err != nil {
if reject != nil {
msg, _ := env.Reject(reject)
return msg.Proto(), reject, err
}
return nil, nil, err
}

if reject, err = env.sealEnvelope(); reject != nil || err != nil {
if reject != nil {
msg, _ := env.Reject(reject)
return msg.Proto(), reject, err
}
return nil, nil, err
}

return env.Proto(), nil, nil
}

// Open a secure envelope using the private key that is paired with the public key that
// was used to seal the envelope (must be supplied via the WithSealingKey or
// WithRSAPrivateKey options). This method decrypts the encryption key and hmac secret,
// decrypts and verifies the payload HMAC signature, then unmarshals the payload and
// verifies its contents. This method returns two types of errors: a rejection error
// that can be returned to the sender to indicate that the TRISA protocol failed,
// otherwise an error is returned for the user to handle. This method is a convenience
// one-liner, for more control of the open envelope process or to manage intermediate
// steps, use the Envelope wrapper directly.
func Open(msg *api.SecureEnvelope, opts ...Option) (payload *api.Payload, reject *api.Error, err error) {
var env *Envelope
if env, err = Wrap(msg, opts...); err != nil {
return nil, nil, err
}

// A rejection here would be related to a sealing key failure
if reject, err = env.unsealEnvelope(); reject != nil || err != nil {
return nil, reject, err
}

// A rejection here is related to the decryption, verification, and parsing the payload
if reject, err = env.decrypt(); reject != nil || err != nil {
return nil, reject, err
}

if payload, err = env.Payload(); err != nil {
return nil, nil, err
}
return payload, nil, nil
}

// Reject returns a new rejection error to send to the counterparty
func Reject(reject *api.Error, opts ...Option) (_ *api.SecureEnvelope, err error) {
var env *Envelope
if env, err = New(nil, opts...); err != nil {
return nil, err
}

// Add the error to the envelope and validate
env.msg.Error = reject

// Determine the transfer state from the rejection
if reject.Retry {
env.msg.TransferState = api.TransferRepair
} else {
env.msg.TransferState = api.TransferRejected
}

// Validate the message and the error
if err = env.ValidateMessage(); err != nil {
return nil, err
}

return env.Proto(), nil
}

// Check returns any error on the specified envelope as well as a bool that indicates
// if the envelope is in an error state (even if the envelope contains a payload).
func Check(msg *api.SecureEnvelope) (_ *api.Error, iserr bool) {
env := &Envelope{msg: msg}
return env.Error(), env.IsError()
}

// Status returns the state the secure envelope is currently in.
func Status(msg *api.SecureEnvelope) State {
env := &Envelope{msg: msg}
return env.State()
}

// Timestamp returns the parsed timestamp from the secure envelope.
func Timestamp(msg *api.SecureEnvelope) (time.Time, error) {
env := &Envelope{msg: msg}
return env.Timestamp()
}

// Envelope is a wrapper for a trisa.SecureEnvelope that adds cryptographic
// functionality to the protocol buffer payload. An envelope can be in one of three
// states: clear, unsealed, and sealed -- referring to the cryptographic status of the
Expand All @@ -190,6 +68,7 @@ type Envelope struct {
payload *api.Payload
crypto crypto.Crypto
seal crypto.Cipher
parent *Envelope
}

//===========================================================================
Expand Down Expand Up @@ -268,14 +147,57 @@ func WrapError(reject *api.Error, opts ...Option) (env *Envelope, err error) {
return env, nil
}

// Validate is a one-liner for Wrap(msg).ValidateMessage() and can be used to ensure
// that a secure envelope has been correctly initialized and can be processed.
func Validate(msg *api.SecureEnvelope) (err error) {
var env *Envelope
if env, err = Wrap(msg); err != nil {
return err
// Create an envelope with an encrypted and sealed payload using the public signing key
// of the TRISA peer (supplied via the WithSealingKey or WithRSAPublicKey options).
// The returned envelope has a parent chain that contains the encryption transformations
// at each step so that you can validate that the payload has been constructed correctly.
func Seal(payload *api.Payload, opts ...Option) (env *Envelope, reject *api.Error, err error) {
if env, err = New(payload, opts...); err != nil {
return nil, nil, err
}

if env, reject, err = env.Encrypt(); err != nil {
if reject != nil {
env, _ = env.Reject(reject)
}
return env, reject, err
}

if env, reject, err = env.Seal(); err != nil {
if reject != nil {
env, _ = env.Reject(reject)
}
return env, reject, err
}

return env, nil, nil
}

// Open a secure envelope using the private key that is paired with the public key used
// to seal the envelope (provided using the WithUnsealingKey or WithRSAPrivateKey
// options). The returned envelope has a partent chain that contains the decryption
// transformations at each step so tha tyou can validate that the payload has been
// constructed correctly.
func Open(msg *api.SecureEnvelope, opts ...Option) (env *Envelope, reject *api.Error, err error) {
if env, err = Wrap(msg, opts...); err != nil {
return nil, nil, err
}

if env, reject, err = env.Unseal(); err != nil {
if reject != nil {
env, _ = env.Reject(reject)
}
return env, reject, err
}

if env, reject, err = env.Decrypt(); err != nil {
if reject != nil {
env, _ = env.Reject(reject)
}
return env, reject, err
}
return env.ValidateMessage()

return env, nil, nil
}

//===========================================================================
Expand All @@ -300,6 +222,7 @@ func (e *Envelope) Reject(reject *api.Error, opts ...Option) (env *Envelope, err
PublicKeySignature: "",
TransferState: api.TransferStateUnspecified,
},
parent: e,
}

if reject.Retry {
Expand Down Expand Up @@ -348,6 +271,7 @@ func (e *Envelope) Update(payload *api.Payload, opts ...Option) (env *Envelope,
crypto: e.crypto,
seal: e.seal,
payload: payload,
parent: e,
}

// Apply the options
Expand Down Expand Up @@ -393,6 +317,7 @@ func (e *Envelope) Encrypt(opts ...Option) (env *Envelope, reject *api.Error, er
},
crypto: e.crypto,
seal: e.seal,
parent: e,
}

// Apply the options
Expand Down Expand Up @@ -443,7 +368,7 @@ func (e *Envelope) encrypt(payload *api.Payload) (_ *api.Error, err error) {
return nil, fmt.Errorf("could not sign payload data: %s", err)
}

// Populate metadata on envelope
// Populate metadata on envelope and reset public key signature since its not present
e.msg.EncryptionKey = e.crypto.EncryptionKey()
e.msg.HmacSecret = e.crypto.HMACSecret()
e.msg.EncryptionAlgorithm = e.crypto.EncryptionAlgorithm()
Expand Down Expand Up @@ -484,6 +409,7 @@ func (e *Envelope) Decrypt(opts ...Option) (env *Envelope, reject *api.Error, er
},
crypto: e.crypto,
seal: e.seal,
parent: e,
}

// Apply the options
Expand Down Expand Up @@ -587,6 +513,7 @@ func (e *Envelope) Seal(opts ...Option) (env *Envelope, reject *api.Error, err e
},
crypto: e.crypto,
seal: e.seal,
parent: e,
}

// Apply the options
Expand Down Expand Up @@ -660,6 +587,7 @@ func (e *Envelope) Unseal(opts ...Option) (env *Envelope, reject *api.Error, err
},
crypto: e.crypto,
seal: e.seal,
parent: e,
}

// Apply the options
Expand Down Expand Up @@ -690,9 +618,10 @@ func (e *Envelope) unsealEnvelope() (reject *api.Error, err error) {
return api.Errorf(api.InvalidKey, "could not unseal HMAC secret").WithRetry(), err
}

// Mark the envelope as unsealed
// Mark the envelope as unsealed and remove the public key signature
e.msg.Sealed = false
e.msg.PublicKeySignature = ""

return nil, nil
}

Expand Down Expand Up @@ -773,6 +702,41 @@ func (e *Envelope) Sealer() crypto.Cipher {
return e.seal
}

// Returns the parent envelope for envelope sequence chain lookups.
func (e *Envelope) Parent() *Envelope {
return e.parent
}

// Finds the public key signature by looking up the envelope tree until a non-zero
// public key signature is available. Returns empty string if none exists. This is
// useful when you receive an envelope and fully decrypt it, but need to refer back to
// the public key signature that was used in the original sealed envelope.
func (e *Envelope) FindPublicKeySignature() string {
switch {
case e.msg.PublicKeySignature != "":
return e.msg.PublicKeySignature
case e.parent != nil:
return e.parent.FindPublicKeySignature()
default:
return ""
}
}

// Find payload returns the nearest payload by looking up the envelope tree until a
// non-nil payload is available. Returns nil if none exists. This is useful when you
// have a payload that is encrypted and sealed, and want to refer back to the original
// payload without keeping track of all of the original envelopes.
func (e *Envelope) FindPayload() *api.Payload {
switch {
case e.payload != nil:
return e.payload
case e.parent != nil:
return e.parent.FindPayload()
default:
return nil
}
}

//===========================================================================
// Envelope Validation
//===========================================================================
Expand Down
Loading

0 comments on commit 9157aba

Please sign in to comment.