Skip to content

Commit

Permalink
TRP Integration Docs (trisacrypto#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort authored Sep 14, 2023
1 parent 01262d7 commit 5f30dea
Show file tree
Hide file tree
Showing 8 changed files with 504 additions and 4 deletions.
22 changes: 18 additions & 4 deletions docs/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,21 @@ enableMissingTranslationPlaceholders = false
weight = 19

[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = false

[markup.goldmark]
[markup.goldmark.renderer]
unsafe = false

[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = false
hl_Lines = ''
hl_inline = false
lineAnchors = ''
lineNoStart = 1
lineNos = false
lineNumbersInTable = true
noClasses = true
noHl = false
style = 'nord'
tabWidth = 4
71 changes: 71 additions & 0 deletions docs/content/openvasp/_index.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: OpenVASP/TRP Integration
date: 2022-06-17T09:15:46-04:00
lastmod: 2022-06-17T16:53:39-04:00
description: "Documentation on how to integrate TRISA with the OpenVASP/TRP Protocol"
weight: 45
---

The [OpenVASP Association](https://www.openvasp.org/) implements an open protocol for travel rule called [TRP](https://www.openvasp.org/trp) that is similar to TRISA but puts more focus on ease of information sharing and human-in-the-loop review than on verified counterparties and cryptography. TRISA and OpenVASP have worked together over the past several months to create a "bridge" that will allow partial compatibility between the two protocols. This page describes how implementers of TRISA nodes might integrate the bridge in their node to respond to TRP requests.

For more information on the TRP protocol, please refer to the [TRP Documentation](https://gitlab.com/OpenVASP/travel-rule-protocol/-/blob/master/core/specification.md?ref_type=heads).

## TRP Workflow

![TRP Workflow](/img/trp_flow.png)

The TRP workflow uses HTTPS `POST` requests with JSON payloads to facilitate information exchange. The initial request endpoint is defined with an LNURL or Travel Address endpoint that the beneficiary must request from their VASP and send to the originator. Subsequent request endpoints are defined with callback URLs in the request itself. HTTP error codes and JSON payloads are used to communicate success or failure back to the counterparty.

In principle, then, a TRISA node must add an HTTP service to its node to accept and respond to these POST requests. TRISA has implemented [handlers and middleware](https://pkg.go.dev/github.com/trisacrypto/[email protected]/pkg/openvasp) in the `trisacrypto/trisa` go package to make it easier to add a service to your TRISA node and to translate TRISA data structures into TRP data structures.

### Next Steps:

1. [Integrating a TRP Bridge Handler into your TRISA node]({{< ref "bridge" >}})
2. [Making Outgoing TRP Requests]({{< ref "client" >}})

## Considerations

As a TRISA node implementer, you have registered for mTLS certificates with TRISA's Global Directory Service and went through a rigorous KYC process to be verified as a VASP that must exchange PII information to comply with the Travel Rule. You are probably used to using the GDS to lookup counterparty endpoints and you've probably experienced significant time and effort implementing key management for the cryptographic requirements that TRISA uses for non-repudiation and secure storage. Counterparties that implement TRP do not have these same requirements.

Therefore, as you implement your TRP integration, you need to consider the following policies and integration standards.

1. **mTLS is a core part of TRP, however, TRP does not specify a Certificate Authority.** Your implementation must consider whether it wants to perform TRP only with TRISA issued certificates or if it is willing to allow other public CAs such as Verisign or Google.
2. **There is no directory of TRP VASPs, TRP discovery is facilitated by Travel Addresses.** TRP uses [Travel Addresses](https://www.21analytics.ch/blog/how-the-trp-travel-address-solves-the-fatf-travel-rule/) to solve counterparty identification. The TRISA bridge is able to parse both LNURLs and the newer Travel Address format, however for complete TRP integration, you will have to supply your accounts with Travel Addresses so that other TRP implementers can reach you as a beneficiary counterparty.
3. **TRP only supports transport-level cryptography, not payload-level cryptography.** There are three levels of cryptography supported by the TRISA bridge: no-cryptography, partial, insecure TRISA envelopes (encrypted but not sealed), and full TRISA compatibility. The first level is plain-vanill TRP and the second two levels are implemented using [TRP Extensions](https://gitlab.com/OpenVASP/travel-rule-protocol/-/tree/master/extensions).

### For TRP Implementers

Welcome, thank you for checking out TRISA! The best thing you can do for integration is to register for TRISA mTLS certificates on [vaspdirectory.net](https://vaspdirectory.net). If you perform mTLS with TRISA VASPs using TRISA certificates, that will help a lot in establishing trust and verification for consideration #1 above.

If you're interested in implementing the extensions for key exchange and parsing secure envelopes, you're more than welcome to use the Golang code in this library to get you started! If you're implementation is in another language, [please let us know](https://github.com/trisacrypto/trisa/issues) so that we can create library code to help you implement secure PII transfers.

Please see the [Getting Started Guide]({{< ref "getting-started" >}}) for more on how to implement TRISA-specific protocol details using the extensions.

### Policies

Given the above considerations, TRISA implementers will have to consider the following policies before TRP integration:

1. Allow certificate authorities other than the TRISA authority for mTLS?
2. Allow native TRP transfers without signatures or payload cryptography?
3. Allow [TRP message signing](https://gitlab.com/OpenVASP/travel-rule-protocol/-/blob/master/extensions/message-signing.md?ref_type=heads) for non-repudiation?
4. Require either TRISA or TRP message signing for non-repudiation?
5. Require public key exchange and secure envelope extension?

The answer to these policies considerations will determine how permissive your node is to accepting different types of transfers, but at the same time create more transfer cases that need to be handled by your node.

### Protocol Comparison

| Feature | TRISA | OpenVASP TRP |
|----------------------|-----------------------------------------------------------------------|----------------------------------------------|
| Governance | Delaware non-profit and technical working group | Swiss non-profit and technical working group |
| Non Repudiation | Signed Envelopes | Signed JSON |
| Data at Rest | Secure Envelopes | |
| Transport Encryption | mTLS 1.3 | mTLS 1.3 |
| Exceptions/Errors | Error codes | HTTP protocol errors |
| Message | Protocol Buffers | JSON |
| Data Types | IVMS101, PayString, Generic | IVMS101 |
| Authentication | X.509 KYV Certs | |
| Transport Protocol | gRPC | HTTPS |
| Addressing | VASP/Account | VASP/Account Travel Address (LNURL) |
| Discovery | Sender provided, receiver verified, blockchain analytics, round robin | Sender provided |
| Onboarding | TRIXO Questionnaire/GDS | |
182 changes: 182 additions & 0 deletions docs/content/openvasp/bridge.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
title: "TRP Bridge"
date: 2023-09-13T16:49:59-05:00
lastmod: 2023-09-13T16:49:59-05:00
description: "Integrating a TRP server into your TRISA node"
weight: 10
---

The TRP Bridge implements [`http.Handler`](https://pkg.go.dev/net/http#Handler) objects for each TRP request type and which can be passed to a basic [`http.Server`](https://pkg.go.dev/net/http#Server) and easily built into your TRISA node. A minimal example that simply logs incoming requests and return no error is below:

```golang
package main

import (
"log"
"net/http"

"github.com/trisacrypto/trisa/pkg/openvasp"
)

// TRPHandler implements both the InquiryHandler and the Confirmation Handler interfaces
type TRPHandler struct{}


// OnInquiry implements the InquiryHandler interface and is used to respond to TRP
// transfer inquiry requests that initiate or conclude the TRP protocol.
func (t *TRPHandler) OnInquiry(in *openvasp.Inquiry) (*openvasp.InquiryResolution, error) {

// TODO: add your Transfer Inquiry code here!
log.Printf(
"received trp inquiry with request identifier %q\n",
in.TRP.RequestIdentifier
)
return nil, nil
}

// OnConfirmation implements the ConfirmationHandler interface and is used to respond to
// TRP callbacks from the beneficiary VASP.
func (t *TRPHandler) OnConfirmation(in *openvasp.Confirmation) error {

// TODO: add your Transfer Confirmation code here!
log.Printf(
"received trp confirmation with request identifier %q\n",
in.TRP.RequestIdentifier
)
return nil
}

func main() {
// Create a new handler object
handler := &TRPHandler{}

// Create a mux to route requests to different paths to different handlers.
mux := http.NewServeMux()
mux.Handle("/transfers", openvasp.TransferInquiry(handler))
mux.Handle("/confirm", openvasp.TransferConfirmation(handler))

log.Printf(
"waiting for TRP requests with API version %s at http://localhost:8080\n",
openvasp.APIVersion
)

// Serve the TRP API server on port 8080. In production applications you would
// likely configure mTLS and TLS termination at the server first.
http.ListenAndServe(":8080", mux)
}
```

## Transfer Inquiry

The [`openvasp.TransferInquiry`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#TransferInquiry) function accepts an object that implements the [`openvasp.InquiryHandler`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#InquiryHandler) interface and returns an [`http.Handler`](https://pkg.go.dev/net/http#Handler) that wraps the [`InquiryHandler`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#InquiryHandler) to handle [TRP Transfer Inquiry `POST` requests](https://gitlab.com/OpenVASP/travel-rule-protocol/-/blob/master/core/specification.md?ref_type=heads#detailed-protocol-flow). The [`InquiryHandler`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#InquiryHandler) is defined as follows:

```golang
type InquiryHandler interface {
OnInquiry(*Inquiry) (*InquiryResolution, error)
}
```

The HTTP handler returned performs the following operations when an incoming HTTP `POST` request is received:

1. Validates the incoming TRP request
2. Parses the TRP [`Inquiry`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#Inquiry) object along with any extensions.
3. Calls the handler's `OnInquiry` method
4. Returns success or failure based on the returned value of `OnInquiry`.

To return a failure condition from the `OnInquiry()` function, users may return an [`openvasp.StatusError`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#StatusError) that specifies the HTTP status code and message to return. This is useful particularly to return `404` errors if the Travel Address is incorrect or no beneficiary account exists at the endpoint. If a generic `error` is returned, then the handler will return a [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) along with the `err.Error()` text.

```golang
func (t *TRPHandler) OnInquiry(in *openvasp.Inquiry) (*openvasp.InquiryResolution, error) {

// Lookup Travel Address and return a 404 error if beneficiary is not found.
if notFound {
return nil, &openvasp.StatusError{Code: http.StatusNotFound}
}

}
```

To return a succesful [200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) response, return an [`openvasp.InquiryResolution`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#InquiryResolution) object or simply `nil, nil`.

The semantics are as follows:

- If the resolution response is `nil` or contains just the `Version`, then the counterparty expects a subsequent `POST` request to the callback in the request.
- The inquiry can be automatically approved by returning the `Approved` field without the `Version` or the `Rejected` fields (these must be zero valued).
- The inquiry can be automatically rejected by specifying a `Rejected` reason without the `Version` or the `Approved` fields (these must be zero valued).


## Transfer Confirmation

The [`openvasp.TransferConfirmation`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#TransferConfirmation) function accepts an object that implements the [`openvasp.ConfirmationHander`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#ConfirmationHandler) interface and returns an `http.Handler` that wraps the [`ConfirmationHander`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#ConfirmationHandler) to handle [TRP Transfer Confirmation `POST` requests](https://gitlab.com/OpenVASP/travel-rule-protocol/-/blob/master/core/specification.md?ref_type=heads#transfer-confirmation). The [`ConfirmationHander`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#ConfirmationHandler) is defined as follows:

```golang
type ConfirmationHandler interface {
OnConfirmation(*Confirmation) error
}
```

When an incoming HTTP `POST` request is received (e.g. to the callback URL specified in the transfer inquiry), the HTTP handler performs the following operations:

1. Validates the incoming TRP request
2. Parses the TRP [`Confirmation`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#Confirmation) object
3. Calls the handler's `OnConfirmation` method
4. Returns a [200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) response if the error is `nil` otherwise returns the `StatusError` or a [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500)

## Setting up a Server

There are many ways to setup an `http.Server` in Golang. Feel free to use a the plain vanilla server or a framework like [Gin](https://github.com/gin-gonic/gin) or an advanced muxer like [HTTPRouter](https://github.com/julienschmidt/httprouter). The handlers defined in the `openvasp` package implement the `http.HandlerFunc` and return an `http.Handler` object and can be used in most Go frameworks.

The primary consideration is mapping URLs (e.g. [multiplexing](https://www.alexedwards.net/blog/an-introduction-to-handlers-and-servemuxes-in-go)) and setting up TLS termination and mTLS authentication. TRISA recommends that you terminate TLS and mTLS at a reverse-proxy for effective load balancing. However, by way of example, here is a simple code snippet to demonstrate setting up mTLS in a Go server.

```golang
func main() {
// Assuming you have your mTLS certificate and keys stored in cert.pem and key.pem
certs, err := os.ReadFile("cert.pem")
if err != nil {
log.Fatal(err)
}

certPool := x509.NewCertPool()
certPool.AppendCertsFromPem(certs)

server := http.Server{
Addr: ":8080",
Handler: mux,
TLSConfig: &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
CliantCAs: certPool,
MinVersion: tls.VersionTLS13,
},
},

if er := server.ListenAndServeTLS("cert.pem", "key.pem"); err != nil {
log.Fatal(err)
}
}
```

For more robust mTLS setup, please see the [trust](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/trust) package in TRISA.

## API Checks

The [`openvasp.APIChecks`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#Inquiry) middleware validates TRP requests and parses header information from the request. Both the `TransferInquiry` and the `TransferConfirmation` handlers implement this middleware.

The checks that are performed include:

1. Ensure the HTTP Method is `POST` otherwise a [405 Method Not Allowed](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405) error is returned.
2. Ensure the TRP API Version header is set and the version is compatible with the implemented TRP version otherwise a [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) error is returned.
3. Ensures that the currently implemented TRP API Version header is set on the outgoing response.
4. Checks that there is a request identifier header on the request, otherwise a [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) error is returned.
5. Ensures that the request identifier is echoed back on the outgoing response.
6. Enforces that the `Content-Type` header is specified and that the content type is `application/json`, otherwise a [415 Unsupported Media Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415) error is returned.

## ParseTRPInfo

The [`openvasp.ParseTRPInfo`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#ParseTRPInfo) function parses TRP-specific headers from the request and adds them to a [`openvasp.TRPInfo`](https://pkg.go.dev/github.com/trisacrypto/trisa/pkg/openvasp#TRPInfo) struct that can be used for TRP processing. A mapping of the headers to the parsed fields is as follows:

| Field | Header | Type | Description |
|-------------------|--------------------|----------|----------------------------------------------------------------------------------|
| Address | | string | The Travel Address, LNURL, or URL of the request |
| APIVersion | api-version | string | Defaults to the APIVersion of the package |
| RequestIdentifier | api-extensions | string | A unique identifier representing the specific transfer (used as the envelope ID) |
| APIExtensions | request-identifier | []string | The comma separated names of any extensions used in the request |
Loading

0 comments on commit 5f30dea

Please sign in to comment.