Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TRP Message Sends #247

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 39 additions & 12 deletions pkg/postman/postman.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,20 @@ const (
// Packets contain both an incoming and an outgoing message and are used to ensure that
// an entire tranfer packet can be correctly constructed for both envelopes.
type Packet struct {
DB models.PreparedTransaction // Database interaction methods
In *Incoming // The incoming message that needs to be decrypted
Out *Outgoing // The outgoing message that needs to be encrypted
Log zerolog.Logger // The log context for more effective logging
Counterparty *models.Counterparty // The remote identified counterparty
Transaction *models.Transaction // The associated transaction with the packet
Peer peers.Peer // The remote peer the transfer is being conducted with
PeerInfo *peers.Info // The peer info for finding the counterparty
User *models.User // The user that created the request, if any
APIKey *models.APIKey // The api key that created the request, if any
Request Direction // Determines if the initial message was incoming or outgoing
Reply Direction // Determines if the reply was incoming or outgoing
DB models.PreparedTransaction // Database interaction methods
In *Incoming // The incoming message that needs to be decrypted
Out *Outgoing // The outgoing message that needs to be encrypted
Log zerolog.Logger // The log context for more effective logging
TravelAddress string // The original travel address (if TRP) to send the packet to
Counterparty *models.Counterparty // The remote identified counterparty
Transaction *models.Transaction // The associated transaction with the packet
Peer peers.Peer // The remote peer the transfer is being conducted with (if TRISA)
PeerInfo *peers.Info // The peer info for finding the counterparty (if TRISA)
User *models.User // The user that created the request, if any
APIKey *models.APIKey // The api key that created the request, if any
Request Direction // Determines if the initial message was incoming or outgoing
Reply Direction // Determines if the reply was incoming or outgoing
protocol string // The protocol the packet is being sent with
}

func Send(payload *api.Payload, envelopeID uuid.UUID, transferState api.TransferState, log zerolog.Logger) (packet *Packet, err error) {
Expand Down Expand Up @@ -143,6 +145,31 @@ func (p *Packet) EnvelopeID() string {
}
}

// Returns the transfer state from the request envelope (e.g. the first envelope in the
// packet) - so if it is an outgoing message, it returns the transfer state from the
// outgoing payload and if it is incoming from the incoming payload.
func (p *Packet) TransferState() api.TransferState {
switch p.Request {
case DirectionIncoming:
return p.In.Envelope.TransferState()
case DirectionOutgoing:
return p.Out.Envelope.TransferState()
default:
panic("request direction not set on packet")
}
}

// Gets the protocol that the packet is being sent with; if no protocol has been set
// then the protocol of the counterparty is used.
func (p *Packet) Protocol() string {
if p.protocol == "" {
if p.Counterparty != nil {
p.protocol = p.Counterparty.Protocol
}
}
return p.protocol
}

// Receive updates the incoming message with the specified secure envelope, e.g. in the
// case where the outgoing message has been sent and this is the reply that was received
// from the remote server.
Expand Down
2 changes: 2 additions & 0 deletions pkg/store/models/counterparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ const (
SourceDirectorySync = "gds"
SourceUserEntry = "user"
SourcePeer = "peer"
ProtocolUnknown = "unknown"
ProtocolTRISA = "trisa"
ProtocolTRP = "trp"
ProtocolSunrise = "sunrise"
)

// TODO: how to incorporate the TRIXO form into this model?
Expand Down
4 changes: 4 additions & 0 deletions pkg/store/sqlite/migrations/0006_protocol_info.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Adds information required for TRP to the database.
BEGIN;

COMMIT;
5 changes: 5 additions & 0 deletions pkg/store/sqlite/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func TestMigrations(t *testing.T) {
Name: "Sunrise Tokens",
Path: "0005_sunrise_tokens.sql",
},
{
ID: 6,
Name: "Protocol Info",
Path: "0006_protocol_info.sql",
},
}

for i, migration := range migrations {
Expand Down
2 changes: 1 addition & 1 deletion pkg/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (s *Server) setupRoutes() (err error) {
{
sunrise.GET("/verify", s.VerifySunriseUser)
sunrise.GET("/accept", s.SunriseMessagePreview)
sunrise.GET("/message", authenticate, s.SendMessageForm)
sunrise.GET("/message", authenticate, authorize(permiss.TravelRuleManage), s.SendMessageForm)
}

// API Routes (Including Content Negotiated Partials)
Expand Down
2 changes: 2 additions & 0 deletions pkg/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/trisacrypto/trisa/pkg/openvasp"
"github.com/trisacrypto/trisa/pkg/openvasp/traddr"
)

Expand All @@ -33,6 +34,7 @@ type Server struct {
url *url.URL
vasp *models.Counterparty
trisa network.Network
trp *openvasp.Client
started time.Time
healthy bool
ready bool
Expand Down
3 changes: 3 additions & 0 deletions pkg/web/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ func (s *Server) DeleteTransaction(c *gin.Context) {
// Transaction Detail Actions
//===========================================================================

// Sends a new envelope as provided by the API user to the counterparty in an existing
// transaction. The transaction should have the protocol and sending details associated
// with it in advance of the send since they will not be specified by the requester.
func (s *Server) SendEnvelopeForTransaction(c *gin.Context) {
var (
err error
Expand Down
114 changes: 114 additions & 0 deletions pkg/web/travelrule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package web

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/trisacrypto/envoy/pkg/postman"
dberr "github.com/trisacrypto/envoy/pkg/store/errors"
"github.com/trisacrypto/envoy/pkg/store/models"
api "github.com/trisacrypto/envoy/pkg/web/api/v1"
"github.com/trisacrypto/trisa/pkg/openvasp/traddr"
)

// SendEnvelope performs the bulk of the work to send a TRISA or TRP transaction to the
// counterparty specified and storing both the outgoing and incoming secure envelopes in
// the database. This method is used to send the prepared transaction, to send envelopes
// for a transaction, and in the accept/reject workflows.
func (s *Server) SendEnvelope(ctx context.Context, packet *postman.Packet) (err error) {
// Step 1: Determine if this is a TRISA or TRP transaction and use the correct handler
// to send the outgoing message (which might be updated during the send process) and to
// receive the incoming reply from the counterparty.
switch packet.Counterparty.Protocol {
case models.ProtocolTRISA:
if err = s.SendTRISATransfer(ctx, packet); err != nil {
return err
}
case models.ProtocolTRP:
if err = s.SendTRPMessage(ctx, packet); err != nil {
return err
}
case models.ProtocolSunrise:
return errors.New("sunrise protocol send is not implemented yet")
default:
return fmt.Errorf("could not send secure envelope: unknown protocol %q", packet.Counterparty.Protocol)
}

// Step 2: Store the outgoing envelope by fetching the public key used to seal the
// incoming envelope from key storage. and saving to the database.
if packet.Out.StorageKey, err = s.trisa.StorageKey(packet.In.PublicKeySignature(), packet.Counterparty.CommonName); err != nil {
// TODO: use the default keys if the incoming key is not known
return fmt.Errorf("could not fetch storage key: %w", err)
}

if err = packet.DB.AddEnvelope(packet.Out.Model()); err != nil {
return fmt.Errorf("could not store outgoing envelope: %w", err)
}

// Step 3: Save incoming envelope to the database (should be encrypted with keys we
// sent during the key exchange process of the transfer).
if err = packet.DB.AddEnvelope(packet.In.Model()); err != nil {
return fmt.Errorf("could not store incoming message: %w", err)
}

return nil
}

func (s *Server) CounterpartyFromTravelAddress(c *gin.Context, address string) (cp *models.Counterparty, err error) {
var (
dst string
dstURI *traddr.URL
)

if dst, err = traddr.Decode(address); err != nil {
c.Error(fmt.Errorf("could not decode travel address %q: %w", address, err))
c.JSON(http.StatusBadRequest, api.Error("could not parse the travel address"))
return nil, err
}

if dstURI, err = traddr.Parse(dst); err != nil {
c.Error(err)
c.JSON(http.StatusBadRequest, api.Error("could not parse travel address url"))
return nil, err
}

if cp, err = s.findCounterparty(c.Request.Context(), dstURI); err != nil {
if errors.Is(err, dberr.ErrNotFound) {
c.Error(fmt.Errorf("could not identify counterparty for %s or %s", dstURI.Hostname(), dstURI.Host))
c.JSON(http.StatusNotFound, api.Error("could not identify counterparty from travel address"))
return nil, err
}

c.Error(err)
c.JSON(http.StatusInternalServerError, api.Error("could not complete request"))
return nil, err
}

return cp, nil
}

func (s *Server) findCounterparty(ctx context.Context, uri *traddr.URL) (cp *models.Counterparty, err error) {
// Lookup counterparty by hostname first (e.g. the common name).
if cp, err = s.store.LookupCounterparty(ctx, uri.Hostname()); err != nil {
if errors.Is(err, dberr.ErrNotFound) {
// If we couldn't find it, try again by endpoint
// NOTE: this is primarily to assist with lookups for localhost where the
// port number is the only differentiating aspect of the node.
if cp, err = s.store.LookupCounterparty(ctx, uri.Host); err != nil {
return nil, dberr.ErrNotFound
}

// Found! Short-circuit the error handling by returning early!
return cp, err
}

// Return the internal error
return nil, err
}

// Found on first try!
return cp, nil
}
104 changes: 1 addition & 103 deletions pkg/web/trisa.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,22 @@ package web

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/trisacrypto/envoy/pkg/postman"
dberr "github.com/trisacrypto/envoy/pkg/store/errors"
"github.com/trisacrypto/envoy/pkg/store/models"
api "github.com/trisacrypto/envoy/pkg/web/api/v1"
"github.com/trisacrypto/trisa/pkg/openvasp/traddr"
trisa "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1"
"github.com/trisacrypto/trisa/pkg/trisa/envelope"
"github.com/trisacrypto/trisa/pkg/trisa/keys"
)

// SendEnvelope performs the bulk of the work to send a TRISA or TRP transaction to the
// counterparty specified and storing both the outgoing and incoming secure envelopes in
// the database. This method is used to send the prepared transaction, to send envelopes
// for a transaction, and in the accept/reject workflows.
func (s *Server) SendEnvelope(ctx context.Context, packet *postman.Packet) (err error) {
// Step 1: Determine if this is a TRISA or TRP transaction and use the correct handler
// to send the outgoing message (which might be updated during the send process) and to
// receive the incoming reply from the counterparty.
switch packet.Counterparty.Protocol {
case models.ProtocolTRISA:
if err = s.SendTRISATransfer(ctx, packet); err != nil {
return err
}
case models.ProtocolTRP:
// TODO: handle TRP transfers
return errors.New("the outgoing TRP send protocol is not implemented yet but is coming soon")
default:
return fmt.Errorf("could not send secure envelope: unknown protocol %q", packet.Counterparty.Protocol)
}

// Step 2: Store the outgoing envelope by fetching the public key used to seal the
// incoming envelope from key storage. and saving to the database.
if packet.Out.StorageKey, err = s.trisa.StorageKey(packet.In.PublicKeySignature(), packet.Counterparty.CommonName); err != nil {
// TODO: use the default keys if the incoming key is not known
return fmt.Errorf("could not fetch storage key: %w", err)
}

if err = packet.DB.AddEnvelope(packet.Out.Model()); err != nil {
return fmt.Errorf("could not store outgoing envelope: %w", err)
}

// Step 3: Save incoming envelope to the database (should be encrypted with keys we
// sent during the key exchange process of the transfer).
if err = packet.DB.AddEnvelope(packet.In.Model()); err != nil {
return fmt.Errorf("could not store incoming message: %w", err)
}

return nil
}

func (s *Server) SendTRISATransfer(ctx context.Context, p *postman.Packet) (err error) {
// Get the peer from the specified counterparty
if p.Peer, err = s.trisa.LookupPeer(ctx, p.Counterparty.CommonName, ""); err != nil {
return fmt.Errorf("could not lookup peer for counterparty %q (%s): %w", p.Counterparty.CommonName, p.Counterparty.ID, err)
}

p.Log = p.Log.With().Str("peer", p.Peer.Name()).Str("envelope_id", p.EnvelopeID()).Logger()
p.Log = p.Log.With().Str("method", "trisa").Str("peer", p.Peer.Name()).Str("envelope_id", p.EnvelopeID()).Logger()
p.Log.Debug().Msg("started outgoing TRISA transfer")

// Add the peer info to the packet
Expand Down Expand Up @@ -116,62 +70,6 @@ func (s *Server) SendTRISATransfer(ctx context.Context, p *postman.Packet) (err
return nil
}

func (s *Server) CounterpartyFromTravelAddress(c *gin.Context, address string) (cp *models.Counterparty, err error) {
var (
dst string
dstURI *traddr.URL
)

if dst, err = traddr.Decode(address); err != nil {
c.Error(fmt.Errorf("could not decode travel address %q: %w", address, err))
c.JSON(http.StatusBadRequest, api.Error("could not parse the travel address"))
return nil, err
}

if dstURI, err = traddr.Parse(dst); err != nil {
c.Error(err)
c.JSON(http.StatusBadRequest, api.Error("could not parse travel address url"))
return nil, err
}

if cp, err = s.findCounterparty(c.Request.Context(), dstURI); err != nil {
if errors.Is(err, dberr.ErrNotFound) {
c.Error(fmt.Errorf("could not identify counterparty for %s or %s", dstURI.Hostname(), dstURI.Host))
c.JSON(http.StatusNotFound, api.Error("could not identify counterparty from travel address"))
return nil, err
}

c.Error(err)
c.JSON(http.StatusInternalServerError, api.Error("could not complete request"))
return nil, err
}

return cp, nil
}

func (s *Server) findCounterparty(ctx context.Context, uri *traddr.URL) (cp *models.Counterparty, err error) {
// Lookup counterparty by hostname first (e.g. the common name).
if cp, err = s.store.LookupCounterparty(ctx, uri.Hostname()); err != nil {
if errors.Is(err, dberr.ErrNotFound) {
// If we couldn't find it, try again by endpoint
// NOTE: this is primarily to assist with lookups for localhost where the
// port number is the only differentiating aspect of the node.
if cp, err = s.store.LookupCounterparty(ctx, uri.Host); err != nil {
return nil, dberr.ErrNotFound
}

// Found! Short-circuit the error handling by returning early!
return cp, err
}

// Return the internal error
return nil, err
}

// Found on first try!
return cp, nil
}

func (s *Server) Decrypt(in *models.SecureEnvelope) (out *envelope.Envelope, err error) {
// No decryption is necessary if this is an error envelope
if in.IsError {
Expand Down
Loading
Loading