Documentation
¶
Overview ¶
Package envelope replaces the handler package to provide utilities for encrypting and decrypting trisa.SecureEnvelopes as well as sealing and unsealing them. SecureEnvelopes are the unit of transfer in a TRISA transaction and are used to securely exchange sensitive compliance information. Security is provided through two forms of cryptography: symmetric cryptography to encrypt and sign the TRISA payload and asymmetric cryptography to seal the keys and secrets of the envelopes so that only the recipient can open it.
SecureEnvelopes have a lot of terminology, the first paragraph was loaded with it! Some of these terms are defined below in relation to SecureEnvelopes:
- Symmetric cryptography: encryption that requires both the sender and receiver to have the same secret keys. The encryption and digital signature of the payload are symmetric, and the encryption key and HMAC secret are stored on the envelope to ensure that both counterparties have the secrets required to decrypt the payload.
- Asymmetric cryptography: also referred to as public key cryptography, this type of cryptography relies on two keys: a public and a private key. When data is encrypted with the public key, only the private key can be used to decrypt the data. In the case of SecureEnvelopes, the encryption key and HMAC secret are encrypted using the public key of the recipient.
- Encrypt/Decrypt: in relation to a SecureEnvelope, this refers to the symmetric cryptography performed on the payload; these terms are used in contrast to Seal/Unseal. An envelope's payload is referred to as "clear" before encryption and after decryption.
- Sign/Verify: in relation to a SecureEnvelope, this refers to the digital signature or HMAC of the encrypted payload. A digital signature ensures that the cryptographic contents of the payload have not been tampered with and provides the counterparty non-repudiation to affirm that the payload was received and not tampered with.
- Seal/Unseal: in relation to a SecureEnvelope, this refers to the asymmetric cryptography performed on the encryption key and hmac secret; these terms are used in contrast to Encrypt/Decrypt.
This package provides a wrapper, Envelope that is used to create and open SecureEnvelopes. The envelope workflow is as follows; a new envelope is in the clear, it is then encrypted and is referred to as "unsealed", then sealed with the public key of the receipient. When opening an envelope with a private key, the envelope becomes unsealed, then is decrypted in the clear.
For more details about how to work with envelopes, see the example code.
Example (Create) ¶
package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "log" "os" "github.com/trisacrypto/trisa/pkg/ivms101" api "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1" generic "github.com/trisacrypto/trisa/pkg/trisa/data/generic/v1beta1" "github.com/trisacrypto/trisa/pkg/trisa/envelope" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" ) func main() { // Create compliance payload to send to counterparty. Use key exchange or GDS to // fetch the public sealing key of the recipient. See the testdata fixtures for // example data. Note: we're loading an RSA private key and extracting its public // key for example and testing purposes. payload, _ := loadPayloadFixture("testdata/payload.json") key, _ := loadPrivateKey("testdata/sealing_key.pem") // Envelopes transition through the following states: clear --> unsealed --> sealed. // First create a new envelope in the clear state with the public key of the // recipient that will eventually be used to seal the envelope. env, _ := envelope.New(payload, envelope.WithRSAPublicKey(&key.PublicKey)) // Marshal the payload, generate random encryption and hmac secrets, and encrypt // the payload, creating a new envelope in the unsealed state. env, reject, err := env.Encrypt() // Two types of errors are returned from Encrypt and Seal if err != nil { if reject != nil { // If both err and reject are non-nil, then a TRISA protocol error occurred // and the rejection error can be sent back to the originator if you're // sealing the envelope in response to a transfer request log.Println(reject.String()) } else { // Otherwise log the error and handle with user-specific code log.Fatal(err) } } // Seal the envelope by encrypting the encryption key and hmac secret on the secure // envelope with the public key of the recipient passed in at the first step. // Handle the reject and err errors as above. env, reject, err = env.Seal() // Fetch the secure envelope and send it. msg := env.Proto() log.Printf("sending secure envelope with id %s", msg.Id) } const expectedEnvelopeId = "2b3b4c95-0a78-4f2a-a9fa-041970f97144" var ( loadpb = protojson.UnmarshalOptions{ AllowPartial: false, DiscardUnknown: false, } dumppb = protojson.MarshalOptions{ Multiline: true, Indent: " ", AllowPartial: true, UseProtoNames: true, UseEnumNumbers: false, EmitUnpopulated: true, } ) // Helper method to load a payload fixture, generating it if it hasn't been yet func loadPayloadFixture(path string) (payload *api.Payload, err error) { payload = &api.Payload{} if err = loadFixture(path, payload, true); err != nil { return nil, err } return payload, nil } // Helper method to load a fixture from JSON func loadFixture(path string, m proto.Message, check bool) (err error) { if check { if _, err = os.Stat(path); os.IsNotExist(err) { if err = generateFixtures(); err != nil { return err } } } var data []byte if data, err = os.ReadFile(path); err != nil { return err } if err = loadpb.Unmarshal(data, m); err != nil { return err } return nil } // Helper method to generate secure envelopes from the payload fixtures func generateFixtures() (err error) { var ( payload *api.Payload pendingPayload *api.Payload ) identity := &ivms101.IdentityPayload{} if err = loadFixture("testdata/payload/identity.json", identity, false); err != nil { return fmt.Errorf("could not unmarshal identity payload: %v", err) } pending := &generic.Pending{} if err = loadFixture("testdata/payload/pending.json", pending, false); err != nil { return fmt.Errorf("could not read pending payload: %v", err) } transaction := &generic.Transaction{} if err = loadFixture("testdata/payload/transaction.json", transaction, false); err != nil { return fmt.Errorf("could not read transaction payload: %v", err) } payload = &api.Payload{ SentAt: "2022-01-27T08:21:43Z", ReceivedAt: "2022-01-30T16:28:39Z", } if payload.Identity, err = anypb.New(identity); err != nil { return fmt.Errorf("could not create identity payload: %v", err) } if payload.Transaction, err = anypb.New(transaction); err != nil { return fmt.Errorf("could not create transaction payload: %v", err) } pendingPayload = &api.Payload{ Identity: payload.Identity, SentAt: payload.SentAt, } if pendingPayload.Transaction, err = anypb.New(pending); err != nil { return fmt.Errorf("could not create pending payload: %v", err) } if err = dumpFixture("testdata/payload.json", payload); err != nil { return fmt.Errorf("could not marshal payload: %v", err) } if err = dumpFixture("testdata/pending_payload.json", pendingPayload); err != nil { return fmt.Errorf("could not marshal pending payload: %v", err) } env := &api.SecureEnvelope{ Id: expectedEnvelopeId, Timestamp: "2022-01-27T08:21:43Z", Error: &api.Error{ Code: api.Error_COMPLIANCE_CHECK_FAIL, Message: "specified account has been frozen temporarily", }, } if err = dumpFixture("testdata/error_envelope.json", env); err != nil { return fmt.Errorf("could not marshal error only envelope: %v", err) } var handler *envelope.Envelope if handler, err = envelope.New(payload); err != nil { return err } if handler, _, err = handler.Encrypt(); err != nil { return err } if err = dumpFixture("testdata/unsealed_envelope.json", handler.Proto()); err != nil { return fmt.Errorf("could not marshal unsealed envelope: %v", err) } key, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return fmt.Errorf("could not generate RSA key fixture") } if err = dumpPrivateKey("testdata/sealing_key.pem", key); err != nil { return err } if env, _, err = envelope.Seal(pendingPayload, envelope.WithRSAPublicKey(&key.PublicKey)); err != nil { return err } if err = dumpFixture("testdata/sealed_envelope.json", env); err != nil { return fmt.Errorf("could not marshal sealed envelope: %v", err) } return nil } func dumpFixture(path string, m proto.Message) (err error) { var data []byte if data, err = dumppb.Marshal(m); err != nil { return err } return os.WriteFile(path, data, 0644) } func dumpPrivateKey(path string, key *rsa.PrivateKey) (err error) { var data []byte if data, err = x509.MarshalPKCS8PrivateKey(key); err != nil { return err } block := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: data, }) return os.WriteFile(path, block, 0600) } func loadPrivateKey(path string) (key *rsa.PrivateKey, err error) { var data []byte if data, err = os.ReadFile(path); err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("could not decode PEM data") } var keyt interface{} if keyt, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } return keyt.(*rsa.PrivateKey), nil }
Output:
Example (Parse) ¶
package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "log" "os" "github.com/trisacrypto/trisa/pkg/ivms101" api "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1" generic "github.com/trisacrypto/trisa/pkg/trisa/data/generic/v1beta1" "github.com/trisacrypto/trisa/pkg/trisa/envelope" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" ) func main() { // Receive a sealed secure envelope from the counterparty. Ensure you have the // private key paired with the public key identified by the public key signature on // the secure envelope in order to unseal and decrypt the payload. See testdata // fixtures for example data. Note: we're loading an RSA private key used in other // examples for demonstration and testing purposes. msg, _ := loadEnvelopeFixture("testdata/sealed_envelope.json") key, _ := loadPrivateKey("testdata/sealing_key.pem") // Envelopes transition through the following states: sealed --> unsealed --> clear. // First wrap the incoming envelope in the sealed state. env, _ := envelope.Wrap(msg) // Unseal the envelope using the private key loaded above; this decrypts the // encryption key and hmac secret using asymmetric encryption and returns a new // unsealed envelope. env, reject, err := env.Unseal(envelope.WithRSAPrivateKey(key)) // Two types of errors are returned from Unseal and Decrypt if err != nil { if reject != nil { // If both err and reject are non-nil, then a TRISA protocol error occurred // and the rejection error can be sent back to the originator if you're // unsealing the envelope in response to a transfer request. out, _ := env.Reject(reject) log.Printf("sending TRISA rejection for envelope %s: %s", out.ID(), reject) } else { // Otherwise log the error and handle with user-specific code log.Fatal(err) } } // Decrypt the envelope using the unsealed secrets, verify the HMAC signature, then // unmarshal and verify the payload into new envelope in the clear state. // Handle the reject and err errors as above. env, reject, err = env.Decrypt() // Handle the payload with your interal compliance processing mechanism. payload, _ := env.Payload() log.Printf("received payload sent at %s", payload.SentAt) } const expectedEnvelopeId = "2b3b4c95-0a78-4f2a-a9fa-041970f97144" var ( loadpb = protojson.UnmarshalOptions{ AllowPartial: false, DiscardUnknown: false, } dumppb = protojson.MarshalOptions{ Multiline: true, Indent: " ", AllowPartial: true, UseProtoNames: true, UseEnumNumbers: false, EmitUnpopulated: true, } ) // Helper method to load a secure envelope fixture, generating the fixtures from the // payloads if they have not yet been generated. func loadEnvelopeFixture(path string) (msg *api.SecureEnvelope, err error) { msg = &api.SecureEnvelope{} if err = loadFixture(path, msg, true); err != nil { return nil, err } return msg, nil } // Helper method to load a fixture from JSON func loadFixture(path string, m proto.Message, check bool) (err error) { if check { if _, err = os.Stat(path); os.IsNotExist(err) { if err = generateFixtures(); err != nil { return err } } } var data []byte if data, err = os.ReadFile(path); err != nil { return err } if err = loadpb.Unmarshal(data, m); err != nil { return err } return nil } // Helper method to generate secure envelopes from the payload fixtures func generateFixtures() (err error) { var ( payload *api.Payload pendingPayload *api.Payload ) identity := &ivms101.IdentityPayload{} if err = loadFixture("testdata/payload/identity.json", identity, false); err != nil { return fmt.Errorf("could not unmarshal identity payload: %v", err) } pending := &generic.Pending{} if err = loadFixture("testdata/payload/pending.json", pending, false); err != nil { return fmt.Errorf("could not read pending payload: %v", err) } transaction := &generic.Transaction{} if err = loadFixture("testdata/payload/transaction.json", transaction, false); err != nil { return fmt.Errorf("could not read transaction payload: %v", err) } payload = &api.Payload{ SentAt: "2022-01-27T08:21:43Z", ReceivedAt: "2022-01-30T16:28:39Z", } if payload.Identity, err = anypb.New(identity); err != nil { return fmt.Errorf("could not create identity payload: %v", err) } if payload.Transaction, err = anypb.New(transaction); err != nil { return fmt.Errorf("could not create transaction payload: %v", err) } pendingPayload = &api.Payload{ Identity: payload.Identity, SentAt: payload.SentAt, } if pendingPayload.Transaction, err = anypb.New(pending); err != nil { return fmt.Errorf("could not create pending payload: %v", err) } if err = dumpFixture("testdata/payload.json", payload); err != nil { return fmt.Errorf("could not marshal payload: %v", err) } if err = dumpFixture("testdata/pending_payload.json", pendingPayload); err != nil { return fmt.Errorf("could not marshal pending payload: %v", err) } env := &api.SecureEnvelope{ Id: expectedEnvelopeId, Timestamp: "2022-01-27T08:21:43Z", Error: &api.Error{ Code: api.Error_COMPLIANCE_CHECK_FAIL, Message: "specified account has been frozen temporarily", }, } if err = dumpFixture("testdata/error_envelope.json", env); err != nil { return fmt.Errorf("could not marshal error only envelope: %v", err) } var handler *envelope.Envelope if handler, err = envelope.New(payload); err != nil { return err } if handler, _, err = handler.Encrypt(); err != nil { return err } if err = dumpFixture("testdata/unsealed_envelope.json", handler.Proto()); err != nil { return fmt.Errorf("could not marshal unsealed envelope: %v", err) } key, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return fmt.Errorf("could not generate RSA key fixture") } if err = dumpPrivateKey("testdata/sealing_key.pem", key); err != nil { return err } if env, _, err = envelope.Seal(pendingPayload, envelope.WithRSAPublicKey(&key.PublicKey)); err != nil { return err } if err = dumpFixture("testdata/sealed_envelope.json", env); err != nil { return fmt.Errorf("could not marshal sealed envelope: %v", err) } return nil } func dumpFixture(path string, m proto.Message) (err error) { var data []byte if data, err = dumppb.Marshal(m); err != nil { return err } return os.WriteFile(path, data, 0644) } func dumpPrivateKey(path string, key *rsa.PrivateKey) (err error) { var data []byte if data, err = x509.MarshalPKCS8PrivateKey(key); err != nil { return err } block := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: data, }) return os.WriteFile(path, block, 0600) } func loadPrivateKey(path string) (key *rsa.PrivateKey, err error) { var data []byte if data, err = os.ReadFile(path); err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("could not decode PEM data") } var keyt interface{} if keyt, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } return keyt.(*rsa.PrivateKey), nil }
Output:
Index ¶
- Variables
- func Check(msg *api.SecureEnvelope) (_ *api.Error, iserr bool)
- func Open(msg *api.SecureEnvelope, opts ...Option) (payload *api.Payload, reject *api.Error, err error)
- func Reject(reject *api.Error, opts ...Option) (_ *api.SecureEnvelope, err error)
- func Seal(payload *api.Payload, opts ...Option) (_ *api.SecureEnvelope, reject *api.Error, err error)
- func Validate(msg *api.SecureEnvelope) (err error)
- type Envelope
- func (e *Envelope) Crypto() crypto.Crypto
- func (e *Envelope) Decrypt(opts ...Option) (env *Envelope, reject *api.Error, err error)
- func (e *Envelope) Encrypt(opts ...Option) (env *Envelope, reject *api.Error, err error)
- func (e *Envelope) Error() *api.Error
- func (e *Envelope) ID() string
- func (e *Envelope) Payload() (_ *api.Payload, err error)
- func (e *Envelope) Proto() *api.SecureEnvelope
- func (e *Envelope) Reject(reject *api.Error, opts ...Option) (env *Envelope, err error)
- func (e *Envelope) Seal(opts ...Option) (env *Envelope, reject *api.Error, err error)
- func (e *Envelope) Sealer() crypto.Cipher
- func (e *Envelope) State() State
- func (e *Envelope) Timestamp() (ts time.Time, err error)
- func (e *Envelope) Unseal(opts ...Option) (env *Envelope, reject *api.Error, err error)
- func (e *Envelope) Update(payload *api.Payload, opts ...Option) (env *Envelope, err error)
- func (e *Envelope) ValidateMessage() error
- func (e *Envelope) ValidatePayload() error
- type Option
- func FromEnvelope(env *Envelope) Option
- func WithAESGCM(encryptionKey []byte, hmacSecret []byte) Option
- func WithCrypto(crypto crypto.Crypto) Option
- func WithEnvelopeID(id string) Option
- func WithRSAPrivateKey(key *rsa.PrivateKey) Option
- func WithRSAPublicKey(key *rsa.PublicKey) Option
- func WithSeal(seal crypto.Cipher) Option
- func WithSealingKey(key interface{}) Option
- func WithTimestamp(ts time.Time) Option
- func WithUnsealingKey(key interface{}) Option
- type State
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNoMessage = errors.New("invalid envelope: no wrapped message") ErrNoEnvelopeId = errors.New("invalid envelope: no envelope id") ErrNoTimestamp = errors.New("invalid envelope: no ordering timestamp") ErrNoMessageData = errors.New("invalid envelope: must contain either error or payload") ErrNoEncryptionInfo = errors.New("invalid envelope: missing encryption key or algorithm") ErrNoHMACInfo = errors.New("invalid envelope: missing hmac signature, secret, or algorithm") ErrNoPayload = errors.New("invalid payload: payload has not been decrypted") ErrNoIdentityPayload = errors.New("invalid payload: payload does not contain identity data") ErrNoTransactionPayload = errors.New("invalid payload: payload does not contain transaction data") ErrNoSentAtPayload = errors.New("invalid payload: sent at timestamp is missing") ErrInvalidSentAtPayload = errors.New("invalid payload: could not parse sent at timestamp in RFC3339 format") ErrInvalidReceivedatPayload = errors.New("invalid payload: could not parse received at timestamp in RFC3339 format") ErrCannotEncrypt = errors.New("cannot encrypt envelope: no cryptographic handler available") ErrCannotSeal = errors.New("cannot seal envelope: no public key cryptographic handler available") ErrCannotUnseal = errors.New("cannot unseal envelope: no private key cryptographic handler available") )
Functions ¶
func Check ¶ added in v0.3.5
func Check(msg *api.SecureEnvelope) (_ *api.Error, iserr bool)
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 Open ¶
func Open(msg *api.SecureEnvelope, opts ...Option) (payload *api.Payload, reject *api.Error, err error)
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.
Example ¶
package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "log" "os" "github.com/trisacrypto/trisa/pkg/ivms101" api "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1" generic "github.com/trisacrypto/trisa/pkg/trisa/data/generic/v1beta1" "github.com/trisacrypto/trisa/pkg/trisa/envelope" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" ) func main() { // Receive a sealed secure envelope from the counterparty. Ensure you have the // private key paired with the public key identified by the public key signature on // the secure envelope in order to unseal and decrypt the payload. See testdata // fixtures for example data. Note: we're loading an RSA private key used in other // examples for demonstration and testing purposes. msg, _ := loadEnvelopeFixture("testdata/sealed_envelope.json") key, _ := loadPrivateKey("testdata/sealing_key.pem") // Open the secure envelope, unsealing the encryption key and hmac secret with the // supplied private key, then decrypting, verifying, and unmarshaling the payload // using those secrets. payload, reject, err := envelope.Open(msg, envelope.WithRSAPrivateKey(key)) // Two types errors may be returned from envelope.Open if err != nil { if reject != nil { // If both err and reject are non-nil, then a TRISA protocol error occurred // and the rejection error can be sent back to the originator if you're // opening the envelope in response to a transfer request out, _ := envelope.Reject(reject, envelope.WithEnvelopeID(msg.Id)) log.Printf("sending TRISA rejection for envelope %s: %s", out.Id, reject) } else { // Otherwise log the error and handle with user-specific code log.Fatal(err) } } // Handle the payload with your interal compliance processing mechanism. log.Printf("received payload sent at %s", payload.SentAt) } const expectedEnvelopeId = "2b3b4c95-0a78-4f2a-a9fa-041970f97144" var ( loadpb = protojson.UnmarshalOptions{ AllowPartial: false, DiscardUnknown: false, } dumppb = protojson.MarshalOptions{ Multiline: true, Indent: " ", AllowPartial: true, UseProtoNames: true, UseEnumNumbers: false, EmitUnpopulated: true, } ) // Helper method to load a secure envelope fixture, generating the fixtures from the // payloads if they have not yet been generated. func loadEnvelopeFixture(path string) (msg *api.SecureEnvelope, err error) { msg = &api.SecureEnvelope{} if err = loadFixture(path, msg, true); err != nil { return nil, err } return msg, nil } // Helper method to load a fixture from JSON func loadFixture(path string, m proto.Message, check bool) (err error) { if check { if _, err = os.Stat(path); os.IsNotExist(err) { if err = generateFixtures(); err != nil { return err } } } var data []byte if data, err = os.ReadFile(path); err != nil { return err } if err = loadpb.Unmarshal(data, m); err != nil { return err } return nil } // Helper method to generate secure envelopes from the payload fixtures func generateFixtures() (err error) { var ( payload *api.Payload pendingPayload *api.Payload ) identity := &ivms101.IdentityPayload{} if err = loadFixture("testdata/payload/identity.json", identity, false); err != nil { return fmt.Errorf("could not unmarshal identity payload: %v", err) } pending := &generic.Pending{} if err = loadFixture("testdata/payload/pending.json", pending, false); err != nil { return fmt.Errorf("could not read pending payload: %v", err) } transaction := &generic.Transaction{} if err = loadFixture("testdata/payload/transaction.json", transaction, false); err != nil { return fmt.Errorf("could not read transaction payload: %v", err) } payload = &api.Payload{ SentAt: "2022-01-27T08:21:43Z", ReceivedAt: "2022-01-30T16:28:39Z", } if payload.Identity, err = anypb.New(identity); err != nil { return fmt.Errorf("could not create identity payload: %v", err) } if payload.Transaction, err = anypb.New(transaction); err != nil { return fmt.Errorf("could not create transaction payload: %v", err) } pendingPayload = &api.Payload{ Identity: payload.Identity, SentAt: payload.SentAt, } if pendingPayload.Transaction, err = anypb.New(pending); err != nil { return fmt.Errorf("could not create pending payload: %v", err) } if err = dumpFixture("testdata/payload.json", payload); err != nil { return fmt.Errorf("could not marshal payload: %v", err) } if err = dumpFixture("testdata/pending_payload.json", pendingPayload); err != nil { return fmt.Errorf("could not marshal pending payload: %v", err) } env := &api.SecureEnvelope{ Id: expectedEnvelopeId, Timestamp: "2022-01-27T08:21:43Z", Error: &api.Error{ Code: api.Error_COMPLIANCE_CHECK_FAIL, Message: "specified account has been frozen temporarily", }, } if err = dumpFixture("testdata/error_envelope.json", env); err != nil { return fmt.Errorf("could not marshal error only envelope: %v", err) } var handler *envelope.Envelope if handler, err = envelope.New(payload); err != nil { return err } if handler, _, err = handler.Encrypt(); err != nil { return err } if err = dumpFixture("testdata/unsealed_envelope.json", handler.Proto()); err != nil { return fmt.Errorf("could not marshal unsealed envelope: %v", err) } key, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return fmt.Errorf("could not generate RSA key fixture") } if err = dumpPrivateKey("testdata/sealing_key.pem", key); err != nil { return err } if env, _, err = envelope.Seal(pendingPayload, envelope.WithRSAPublicKey(&key.PublicKey)); err != nil { return err } if err = dumpFixture("testdata/sealed_envelope.json", env); err != nil { return fmt.Errorf("could not marshal sealed envelope: %v", err) } return nil } func dumpFixture(path string, m proto.Message) (err error) { var data []byte if data, err = dumppb.Marshal(m); err != nil { return err } return os.WriteFile(path, data, 0644) } func dumpPrivateKey(path string, key *rsa.PrivateKey) (err error) { var data []byte if data, err = x509.MarshalPKCS8PrivateKey(key); err != nil { return err } block := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: data, }) return os.WriteFile(path, block, 0600) } func loadPrivateKey(path string) (key *rsa.PrivateKey, err error) { var data []byte if data, err = os.ReadFile(path); err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("could not decode PEM data") } var keyt interface{} if keyt, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } return keyt.(*rsa.PrivateKey), nil }
Output:
func Seal ¶
func Seal(payload *api.Payload, opts ...Option) (_ *api.SecureEnvelope, reject *api.Error, err error)
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.
Example ¶
package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "log" "os" "github.com/trisacrypto/trisa/pkg/ivms101" api "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1" generic "github.com/trisacrypto/trisa/pkg/trisa/data/generic/v1beta1" "github.com/trisacrypto/trisa/pkg/trisa/envelope" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" ) func main() { // Create compliance payload to send to counterparty. Use key exchange or GDS to // fetch the public sealing key of the recipient. See the testdata fixtures for // example data. Note: we're loading an RSA private key and extracting its public // key for example and testing purposes. payload, _ := loadPayloadFixture("testdata/payload.json") key, _ := loadPrivateKey("testdata/sealing_key.pem") // Seal the payload: encrypting and digitally signing the marshaled protocol buffers // with a randomly generated encryption key and HMAC secret, then encrypting those // secrets with the public key of the recipient. msg, reject, err := envelope.Seal(payload, envelope.WithRSAPublicKey(&key.PublicKey)) // Two types errors may be returned from envelope.Seal if err != nil { if reject != nil { // If both err and reject are non-nil, then a TRISA protocol error occurred // and the rejection error can be sent back to the originator if you're // sealing the envelope in response to a transfer request log.Println(reject.String()) } else { // Otherwise log the error and handle with user-specific code log.Fatal(err) } } // Otherwise send the secure envelope to the recipient log.Printf("sending secure envelope with id %s", msg.Id) } const expectedEnvelopeId = "2b3b4c95-0a78-4f2a-a9fa-041970f97144" var ( loadpb = protojson.UnmarshalOptions{ AllowPartial: false, DiscardUnknown: false, } dumppb = protojson.MarshalOptions{ Multiline: true, Indent: " ", AllowPartial: true, UseProtoNames: true, UseEnumNumbers: false, EmitUnpopulated: true, } ) // Helper method to load a payload fixture, generating it if it hasn't been yet func loadPayloadFixture(path string) (payload *api.Payload, err error) { payload = &api.Payload{} if err = loadFixture(path, payload, true); err != nil { return nil, err } return payload, nil } // Helper method to load a fixture from JSON func loadFixture(path string, m proto.Message, check bool) (err error) { if check { if _, err = os.Stat(path); os.IsNotExist(err) { if err = generateFixtures(); err != nil { return err } } } var data []byte if data, err = os.ReadFile(path); err != nil { return err } if err = loadpb.Unmarshal(data, m); err != nil { return err } return nil } // Helper method to generate secure envelopes from the payload fixtures func generateFixtures() (err error) { var ( payload *api.Payload pendingPayload *api.Payload ) identity := &ivms101.IdentityPayload{} if err = loadFixture("testdata/payload/identity.json", identity, false); err != nil { return fmt.Errorf("could not unmarshal identity payload: %v", err) } pending := &generic.Pending{} if err = loadFixture("testdata/payload/pending.json", pending, false); err != nil { return fmt.Errorf("could not read pending payload: %v", err) } transaction := &generic.Transaction{} if err = loadFixture("testdata/payload/transaction.json", transaction, false); err != nil { return fmt.Errorf("could not read transaction payload: %v", err) } payload = &api.Payload{ SentAt: "2022-01-27T08:21:43Z", ReceivedAt: "2022-01-30T16:28:39Z", } if payload.Identity, err = anypb.New(identity); err != nil { return fmt.Errorf("could not create identity payload: %v", err) } if payload.Transaction, err = anypb.New(transaction); err != nil { return fmt.Errorf("could not create transaction payload: %v", err) } pendingPayload = &api.Payload{ Identity: payload.Identity, SentAt: payload.SentAt, } if pendingPayload.Transaction, err = anypb.New(pending); err != nil { return fmt.Errorf("could not create pending payload: %v", err) } if err = dumpFixture("testdata/payload.json", payload); err != nil { return fmt.Errorf("could not marshal payload: %v", err) } if err = dumpFixture("testdata/pending_payload.json", pendingPayload); err != nil { return fmt.Errorf("could not marshal pending payload: %v", err) } env := &api.SecureEnvelope{ Id: expectedEnvelopeId, Timestamp: "2022-01-27T08:21:43Z", Error: &api.Error{ Code: api.Error_COMPLIANCE_CHECK_FAIL, Message: "specified account has been frozen temporarily", }, } if err = dumpFixture("testdata/error_envelope.json", env); err != nil { return fmt.Errorf("could not marshal error only envelope: %v", err) } var handler *envelope.Envelope if handler, err = envelope.New(payload); err != nil { return err } if handler, _, err = handler.Encrypt(); err != nil { return err } if err = dumpFixture("testdata/unsealed_envelope.json", handler.Proto()); err != nil { return fmt.Errorf("could not marshal unsealed envelope: %v", err) } key, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return fmt.Errorf("could not generate RSA key fixture") } if err = dumpPrivateKey("testdata/sealing_key.pem", key); err != nil { return err } if env, _, err = envelope.Seal(pendingPayload, envelope.WithRSAPublicKey(&key.PublicKey)); err != nil { return err } if err = dumpFixture("testdata/sealed_envelope.json", env); err != nil { return fmt.Errorf("could not marshal sealed envelope: %v", err) } return nil } func dumpFixture(path string, m proto.Message) (err error) { var data []byte if data, err = dumppb.Marshal(m); err != nil { return err } return os.WriteFile(path, data, 0644) } func dumpPrivateKey(path string, key *rsa.PrivateKey) (err error) { var data []byte if data, err = x509.MarshalPKCS8PrivateKey(key); err != nil { return err } block := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: data, }) return os.WriteFile(path, block, 0600) } func loadPrivateKey(path string) (key *rsa.PrivateKey, err error) { var data []byte if data, err = os.ReadFile(path); err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, fmt.Errorf("could not decode PEM data") } var keyt interface{} if keyt, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } return keyt.(*rsa.PrivateKey), nil }
Output:
func Validate ¶
func Validate(msg *api.SecureEnvelope) (err error)
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.
Types ¶
type Envelope ¶
type Envelope struct {
// contains filtered or unexported fields
}
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 wrapped secure envelope. For example, a clear envelope can have its payload directly read, but if an envelope is unsealed, then it must be decrypted before the payload can be parsed into specific data structures. Similarly, an unsealed envelope can be sealed in preparation for sending to a recipient, or remain unsealed for secure long term data storage.
func New ¶
New creates a new Envelope from scratch with the associated payload and options. New should be used to initialize a secure envelope to send to a recipient, usually before the first transfer of an information exchange, Open is used thereafter.
func Wrap ¶
func Wrap(msg *api.SecureEnvelope, opts ...Option) (env *Envelope, err error)
Wrap initializes an Envelope from an incoming secure envelope without modifying the original envelope. The Envelope can then be inspected and managed using cryptographic and accessor functions.
func (*Envelope) Crypto ¶
Crypto returns the cryptographic method used to encrypt/decrypt the payload.
func (*Envelope) Encrypt ¶
Encrypt the envelope by marshaling the payload, encrypting, and digitally signing it. If the original envelope does not have a crypto method (either by the user supplying one via options or from an incoming secure envelope) then a new AESGCM crypto is created with a random encryption key and hmac secret. Encrypt returns a new unsealed envelope that maintains the original crypto and cipher mechanisms. Two types of errors may be returned: a rejection error is intended to be returned to the sender due to some protocol-specific issue, otherwise an error is returned if the user has made some error that requires rehandling of the envelope. The original envelope is not modified, the secure envelope is cloned.
func (*Envelope) Payload ¶
Payload returns the parsed trisa.Payload protocol buffer if available. If the envelope is not decrypted then an error is returned.
func (*Envelope) Proto ¶
func (e *Envelope) Proto() *api.SecureEnvelope
Proto returns the trisa.SecureEnvelope protocol buffer.
func (*Envelope) Reject ¶
Reject returns a new secure envelope that contains a TRISA rejection error but no payload. The original envelope is not modified, the secure envelope is cloned.
func (*Envelope) Seal ¶
Seal the envelope using public key cryptography so that the envelope can only be decrypted by the recipient. This method encrypts the encryption key and hmac secret using the supplied public key, marking the secure envelope as sealed and updates the signature of the public key used to seal the secure envelope. Two types of errors may be returned from this method: a rejection error used to communicate to the sender that something went wrong and they should resend the envelope or an error that the user should handle in their own code base. The original envelope is not modified, the secure envelope is cloned.
func (*Envelope) Sealer ¶
Sealer returns the cryptographic cipher method used to seal/unseal the envelope.
func (*Envelope) Timestamp ¶
Timestamp returns the ordering timestamp of the secure envelope. If the timestamp is not on the envelope or it cannot be parsed, an error is returned.
func (*Envelope) Unseal ¶
Unseal the envelope using public key cryptography so that the envelope can be opened and decrypted. This method requires a sealing private key and will return an error if one is not available. If the envelope is not able to be opened because the secure envelope contains an unknown or improper state a rejection error is returned to communicate back to the sender that the envelope could not be unsealed. The original envelope is not modified, the secure envelope is cloned.
func (*Envelope) Update ¶
Update the envelope with a new payload maintaining the original crypto method. This is useful to prepare a response to the user, for example updating the ReceivedAt timestamp in the payload then re-encrypting the secure envelope to send back to the originator. Most often, this method is also used with the WithSealingKey option so that the envelope workflow for sealing an envelope can be applied completely. The original envelope is not modified, the secure envelope is cloned.
func (*Envelope) ValidateMessage ¶
ValidateMessage returns an error if the secure envelope does not have the required fields to send.
func (*Envelope) ValidatePayload ¶
ValidatePayload returns an error if the payload is not ready to be encrypted. TODO: should we parse the types of the payload to ensure they're TRISA types?
type Option ¶
func FromEnvelope ¶
func WithAESGCM ¶
func WithCrypto ¶
func WithEnvelopeID ¶
func WithRSAPrivateKey ¶
func WithRSAPrivateKey(key *rsa.PrivateKey) Option
func WithRSAPublicKey ¶
func WithSealingKey ¶
func WithSealingKey(key interface{}) Option
func WithTimestamp ¶
func WithUnsealingKey ¶
func WithUnsealingKey(key interface{}) Option
type State ¶
type State uint16
const ( Unknown State = iota Clear // The envelope has been decrypted and the payload is available on it Unsealed // The envelope is unsealed and can be decrypted without any other information Sealed // The envelope is sealed and must be unsealed with a private key or it is ready to send Error // The envelope does not contain a payload but does contain an error field ClearError // The envelope contains both a decrypted payload and an error UnsealedError // The envelope contains both an error and a payload and is unsealed SealedError // The envelope contains both an error and a payload and is sealed Corrupted // The envelope is in an invalid state and cannot be moved into a correct state )
func Status ¶ added in v0.3.5
func Status(msg *api.SecureEnvelope) State
Status returns the state the secure envelope is currently in.