diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e086008..3ca72ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: go-version: 1.16 - name: Install Staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@latest + run: go install honnef.co/go/tools/cmd/staticcheck@v0.2.2 - name: Checkout Code uses: actions/checkout@v2 @@ -47,7 +47,7 @@ jobs: - name: Install Dependencies run: | go version - go get -u github.com/kevinburke/go-bindata/... + go install github.com/kevinburke/go-bindata/go-bindata@v3.23.0 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 @@ -77,7 +77,7 @@ jobs: - name: Install Dependencies run: | go version - go get -u github.com/kevinburke/go-bindata/... + go install github.com/kevinburke/go-bindata/go-bindata@v3.23.0 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 diff --git a/docs/content/reference/glossary.en.md b/docs/content/reference/glossary.en.md index 380d885..02a9a21 100644 --- a/docs/content/reference/glossary.en.md +++ b/docs/content/reference/glossary.en.md @@ -1,13 +1,44 @@ --- title: Glossary date: 2021-06-14T15:59:09-05:00 -lastmod: 2021-06-14T15:59:09-05:00 +lastmod: 2022-03-31T16:10:07-05:00 description: "TRISA Glossary and Terminology" weight: 20 --- +## General Terminology + - **Originator**: The initiator of a blockchain transaction and therefore also the initiator of a TRISA transfer. The "originator" can refer to the originating VASP, the originating customer of the VASP or both. - **Beneficiary**: The recipient of a blockchain transaction and therefore also the recipient of a TRISA transfer. The "beneficiary" can refer to the beneficiary VASP, the beneficiary customer of the VASP or both. -- **Local vs Remote VASP**: A reference to the source of peer-to-peer traffic in an information exchange. The "local VASP" usually refers to the service you are running, while the "remote VASP" usually refers to some other VASP in the TRISA network. Local vs. Remote is often used to condition originator vs. beneficiary. If you are initiating the transaction then the local VASP is the originator and the remote VASP is the beneficiary. If you are receiving a transaction then the local VASP is the beneficiary and the remote VASP is the originator. \ No newline at end of file +- **Local vs Remote VASP**: A reference to the source of peer-to-peer traffic in an information exchange. The "local VASP" usually refers to the service you are running, while the "remote VASP" usually refers to some other VASP in the TRISA network. Local vs. Remote is often used interchangeably with originator vs. beneficiary. If you are initiating the transaction then the local VASP is the originator and the remote VASP is the beneficiary. If you are receiving a transaction then the local VASP is the beneficiary and the remote VASP is the originator. + +- **Travel Rule**: Record-keeping rules for transfers between financial institutions that allow law enforcement agencies to prevent illicit finance (e.g. money laundering or the financing of terrorism). + +- **VASP**: Virtual Asset Service Provider. A legal entity (usually a business) that manages and transfers virtual assets and are required by the Travel Rule to conduct information exchanges. Compliance exchanges in the TRISA network are between VASPs. + + +## Cryptographic Terminology + +- **mTLS**: Mutual Transport Layer Security is an encryption protocol that authenticates both the client and the server in a network connection and encrypts communications between the parties so that data cannot be read in flight. mTLS is an extention of TLS (formerly SSL) that requires both sides of a network connection to have a certificate that establishes their identity and which can be used to encrypt packets sent on the channel. + +- **Symmetric Cryptography**: Both encryption and decryption of data are performed using a single secret key that must be shared by the sender and the recipient. Shared secrets introduce the problem of how to share the secret key, however symmetric cryptographic algorithms are usually faster and better for bulk encryption of larger amounts of data. Generally, secrets are shared by asymmetric-key encryption and data encrypted using symmetric encryption. + +- **Secret Key Cryptography**: See _symmetric cryptography_. + +- **Public Key Cryptography**: A cryptographic method that uses a pair of related keys. Each pair consists of a _public key_, which can be shared with others, and a _private key_, which must not be shared with anyone but the owner. In practice, data can be encrypted with a public key but only decrypted with the private key. + +- **Asymmetric Cryptography**: See _public key cryptography_. + +- **Digital Signature**: A mathematical method that produces a _signature_ of data, e.g. some other piece of data that summarizes or describes the original data, usually via a hashing method. If the original data changes, its digital signature will change, therefore digital signatures are generally used as proof that the original data has not been tampered with, particularly if the signature has been generated cryptographically. For example, if a certificate has a signature that is signed with the private key of the certificate authority, anyone with the CA's public key can verify the signature of the certificate, ensuring it was the certificate produced by the CA. + +- **HMAC**: Hash Message Authentication Code. A secure method of producing a digital signature for data that uses a cryptographic hash function and a secret key. HMACs are used to verify that data has not been modified or changed, see also _digital signature_. + +- **Certificate**: Usually a reference to an X.509 certificate, a standard format for public key infrastructure. An X.509 certificate is a digital document that securely associates cryptographic key pairs with identities. Certificates are signed by a certificate authority to guarantee their provenance and contain subject information and other metadata concerning , the keypair, and its usage. Generally a reference to a certificate refers to the public key -- the part of the certificate that is shared for authentication purposes. However when certificates are issued, they are issued as a public/private key pair. + +- **Identity Certificate**: a TRISA specific term that refers to the certificate issued by the TRISA CA to a VASP entity that they should use to connect to other VASPs in the TRISA network via mTLS. + +- **Sealing Certificate (or keys)**: a TRISA specific term that refers to a key pair that is used to seal secure envelopes in the TRISA protocol. Sealing keys may be certificates issued by the TRISA CA, or they may be keys generated by the VASP and exchanged during the TRISA protocol. + +- **Certificate Authority**: an entity that issues and revokes certificates and whose public keys are used to establish trust in the identities its issued certificates provide. Certificate authorities usually control cryptographic hardware and the "root of trust" -- a digital key pair that is used to generate and sign intermediate and leaf certificates for public key infrastructure. \ No newline at end of file diff --git a/docs/content/secure-envelopes/_index.en.md b/docs/content/secure-envelopes/_index.en.md new file mode 100644 index 0000000..7c3331e --- /dev/null +++ b/docs/content/secure-envelopes/_index.en.md @@ -0,0 +1,112 @@ +--- +title: Secure Envelopes +date: 2022-03-03T15:36:09-06:00 +lastmod: 2022-03-03T15:36:09-06:00 +description: "Working with Secure Envelopes and cryptography in the TRISA protocol." +weight: 21 +--- + +![Secure Envelopes](/img/secure_envelopes.png) + + +The primary data structure for a TRISA exchange is the `SecureEnvelope` -- a wrapper for compliance payload data that facilitates peer-to-peer trust in compliance information exchanges. The design of TRISA secure envelopes had the following requirements: + +1. **Privacy**: only the recipient of the secure envelope should be able to open the secure envelope, even outside of the context of an RPC that is secured by transport layer encryption. +2. **Non-repudiation**: the secure envelope should cryptographically guarantee that the compliance payload is valid and has not been modified or tampered with since the original exchange. +3. **Long-term data storage**: the secure envelope should be encrypted at rest and can be "erased" by deleting its associated private keys when the compliance statute is over (usually 5-7 years). +4. **Security**: secure envelopes should prevent statistical attacks on the encrypted payload data while using public-key cryptography for ease of key management. + +These requirements are essential to understanding how to successfully engage with TRISA peers, therefore understanding the `SecureEnvelope` is the first step to being able to implement the TRISA protocol. + +## Working with Secure Envelopes + +There are two basic workflows for secure envelopes: creating and sealing an envelope to send to a counterparty, or unsealing and parsing a received secure envelope. + +### Creating a Secure Envelope + +**Prerequisites**: + +1. You should have constructed an appropriate TRISA `Payload` that contains an `identity` (an IVMS 101 `IdentityPayload`), a `transaction` (a TRISA generic transaction) and a `sent_at` timestamp (RFC-3339 formatted). +2. You should have the _public sealing key_ of the receipient. You can obtain this key either via the `KeyExchange` RPC or by requesting the key from the [directory service]({{< ref "/gds" >}}). + +**Steps**: + +1. Create a new envelope with a uuid4 envelope ID and the current timestamp +2. Marshal the `Payload` protocol buffers into an array of bytes. +3. Generate an encryption key and encrypt the payload bytes. +4. Generate an hmac secret and sign the encrypted payload +5. Use the public sealing key of the recipient to encrypt the encryption key and hmac secret. +6. Mark the envelope as sealed and populate all required metadata. + +### Opening a Secure Envelope + +1. Use the `public_key_signature` to identify the private key required to decrypt the encryption key and hmac secret. +2. Use your _private sealing key_ to decrypt the encryption key and HMAC secret. +3. Use the hmac secret and hmac algorithm to verify the encrypted payload has not been tampered with by ensuring the hmac you generate is identical to the hmac on the envelope. +4. Use the encryption key and encryption algorithm to decrypt the payload. +5. Unmarshal the payload into a TRISA `Payload` object. +6. Unmarshal the `identity` and `transaction` payloads and verify that you can parse them into data structures you can use for your compliance workflow. + +### Envelope States + +As you can see from the above workflows, envelopes can be in one of three states: + +1. **Sealed**: the encryption key and hmac secret on the envelope are encrypted with the public key of the recipient. Only the recipient can open the envelope in this state. +2. **Unsealed**: the encryption key and hmac secret are in the clear and can be used to decrypt the payload and verify the HMAC. +3. **Clear**: the payload has been decrypted and can be unmarshaled into a TRISA `Payload` protocol buffer. + +Generally speaking, when working with secure envelopes, "sealing" an envelope moves it through the following states: + +``` +Payload --> Clear --> Unsealed --> Sealed +``` + +Conversely opening an envelope moves it through the following states: + +``` +Sealed --> Unsealed --> Clear --> Payload +``` + +Maintaining envelopes in these various states can be useful to different applications. For example, an unsealed or clear envelope can be used to move data inside of an application while maintaining associated TRISA metdata. A sealed envelope can be used for long-term storage and with proper key-management, ensure erasure of the envelope simply by deleting the keys. + +## The Anatomy of a Secure Envelope + +A `SecureEnvelope` contains envelope metadata, cryptographic metadata, and an encrypted payload and HMAC signature. There are two types of complete envelope: + +1. An error envelope containing complete envelope metadata and a TRISA error. This envelope is sent as a rejection or transfer control message back to the sender from the recipient. +2. A payload envelope that has complete envelope and cryptographic metadata, as well as an encrypted payload and HMAC signature, but without an error. Payload envelopes can be either "sealed" or "unsealed" as described above. + +### Envelope Metadata + +| Field | Definition | +|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | also referred to as the "envelope id" - this is a unique ID generated by the originator and must be identical on all envelopes referring to the same virtual asset transaction. The originator usually generates this ID as a [UUID4](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)). | +| `timestamp` | a nanosecond resolution RFC-3339 formatted timestamp (e.g. [`RFC3339Nano`](https://pkg.go.dev/time#RFC3339Nano)) that is used to order messages with the same ID. | +| `error` | a TRISA-specific error that is intended to help facilitate compliance exchanges. TRISA [error codes](https://github.com/trisacrypto/trisa/blob/main/proto/trisa/api/v1beta1/errors.proto) are used to reject compliance data or to request a fix and retry in a follow-on secure envelope. | + +### Cryptographic Metadata + +| Field | Definition | +|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `encryption_key` | the key used to encrypt the compliance payload. Keys are generated by the originator using a crypto random method. To prevent statistical attacks, a new key is generated for every transfer (e.g. at the same time as the envelope ID). If the envelope is sealed the `encryption_key` is encrypted using the public sealing key of the recipient. | +| `encryption_algorithm` | a string that describes the algorithm used to encrypt the compliance payload. This string should provide enough information for the recipient to be able to decrypt the payload. The default in the reference implementation is `"AES256-GCM"` which describes the use of [ AES Galois Counter Mode ]( https://datatracker.ietf.org/doc/html/rfc5288 ) with a 32 byte key. | +| `hmac_secret` | the secret used to calculate the HMAC signature. This secret is generated by the originator using a crypto random method, and is generated for every transfer. If the envelope is sealed then the `hmac_secret` is encrypted using the public sealing key of the recipient. | +| `hmac_algorithm` | a string that describes the algorithm used to compute the HMAC signature. The default in the reference implementation is `"HMAC-SHA256"` which describes the use of the [ HMAC ]( https://en.wikipedia.org/wiki/HMAC ) algorithm with a [ SHA-256 ]( https://en.wikipedia.org/wiki/SHA-2 ) secure hashing function. | +| `sealed` | a boolean that describes the state of the envelope. If true, this means that the `encryption_key` and `hmac_secret` have been encrypted using the public sealing key of the recipient. | +| `public_key_signature` | the signature of the public key used to seal the envelope, a helper for the recipient to identify the private key required to unseal the envelope. | + +### Payload + +| Field | Definition | +|-----------|-----------------------------------------------------------------------------------------------| +| `payload` | the ` Payload ` protocol buffer marshaled to bytes and encrypted using the ` encryption_key`. | +| `hmac` | the HMAC signature computed from the encrypted payload bytes and the ` hmac_secret`. | + +The `Payload` protocol buffer has the following fields: + +| Field | Definition | +|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `identity` | a protobuf [ any ]( https://developers.google.com/protocol-buffers/docs/proto3#any ) that contains the compliance identity information of the originator and the beneficiary. Although this can be any message type, it should be an [ IVMS101 Identity Payload ]( https://intervasp.org ). | +| `transaction` | a protobuf [ any ]( https://developers.google.com/protocol-buffers/docs/proto3#any ) that contains information used to identify the associated transaction on the blockchain or to send control flow messages and handling-specific instructions. Use one of the TRISA defined [generic transaction data structures](https://github.com/trisacrypto/trisa/blob/main/proto/trisa/data/generic/v1beta1/transaction.proto). | +| `sent_at` | The RFC-3339 formatted timestamp that the originator sent the first compliance message to the beneficiary. This timestamp is part of the compliance payload for non-repudiation purposes. | +| `received_at` | The RFC-3339 formatted timestamp when the beneficiary accepted the compliance message and returned the completed payload to the originator. This timestamp is part of the compliance payload for non-repudiation purposes. | \ No newline at end of file diff --git a/docs/static/img/secure_envelopes.png b/docs/static/img/secure_envelopes.png new file mode 100644 index 0000000..fd73849 Binary files /dev/null and b/docs/static/img/secure_envelopes.png differ diff --git a/pkg/ivms101/testdata/identity_payload.json b/pkg/ivms101/testdata/identity_payload.json new file mode 100644 index 0000000..05fa136 --- /dev/null +++ b/pkg/ivms101/testdata/identity_payload.json @@ -0,0 +1,158 @@ +{ + "originator": { + "originatorPersons": [{ + "naturalPerson": { + "name": { + "nameIdentifier": [{ + "primaryIdentifier": "Howard", + "secondaryIdentifer": "Jane", + "nameIdentifierType": "LEGL" + }, + { + "primaryIdentifier": "Price", + "secondaryIdentifer": "Jane", + "nameIdentifierType": "MAID" + } + ] + }, + "geographicAddress": [{ + "addressType": "HOME", + "streetName": "Greystone Street", + "buildingNumber": "28", + "postCode": "38017", + "townName": "Collierville", + "countrySubDivision": "TN", + "country": "US" + }], + "nationalIdentification": { + "nationalIdentifier": "112502920", + "nationalIdentifierType": "SOCS", + "countryOfIssue": "US", + "registrationAuthority": "RA000748" + }, + "customerIdentification": "2642", + "dateAndPlaceOfBirth": { + "dateOfBirth": "1992-10-04", + "placeOfBirth": "West Islip, NY" + }, + "countryOfResidence": "US" + } + }], + "accountNumber": [ + "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v" + ] + }, + "beneficiary": { + "beneficiaryPersons": [{ + "naturalPerson": { + "name": { + "nameIdentifier": [{ + "primaryIdentifier": "Clark", + "secondaryIdentifier": "Lawrence", + "nameIdentifierType": "LEGL" + }, + { + "primaryIdentifier": "Clark", + "secondaryIdentifier": "Larry", + "nameIdentifierType": "ALIA" + } + ] + }, + "geographicAddress": [{ + "addressType": "HOME", + "streetName": "Watling St", + "buildingNumber": "249", + "postCode": "WD7 7AL", + "townName": "Radlett", + "country": "United Kingdom" + }], + "nationalIdentification": { + "nationalIdentifier": "319560446", + "nationalIdentifierType": "DRLC", + "country_of_issue": "GB", + "registration_authority": "RA000591" + }, + "customerIdentification": "5610", + "dateAndPlaceOfBirth": { + "dateOfBirth": "1986-12-13", + "placeOfBirth": "Leeds, United Kingdom" + }, + "countryOfResidence": "GB" + } + }], + "accountNumber": [ + "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3" + ] + }, + "originatingVASP": { + "legalPerson": { + "name": { + "nameIdentifiers": [{ + "legalPersonName": "AliceCoin, Inc.", + "legalPersonNameIdentifierType": "LEGL" + }, + { + "legalPersonName": "Alice VASP", + "legalPersonNameIdentifierType": "SHRT" + }, + { + "legalPersonName": "AliceCoin", + "legalPersonNameIdentifierType": "TRAD" + } + ] + }, + "geographicAddresses": [{ + "addressType": "BIZZ", + "streetName": "Roosevelt Place", + "buildingNumber": "23", + "postCode": "02151", + "townName": "Boston", + "countrySubDivision": "MA", + "country": "US" + }], + "nationalIdentification": { + "nationalIdentifier": "5493004YBI24IF4TIP92", + "nationalIdentifierType": "LEIX", + "countryOfIssue": "US", + "registrationAuthority": "RA000744" + }, + "countryOfRegistration": "US" + } + }, + "beneficiaryVASP": { + "legalPerson": { + "name": { + "nameIdentifiers": [{ + "legalPersonName": "Bob's Discount VASP, PLC", + "legalPersonNameIdentifierType": "LEGL" + }, + { + "legalPersonName": "Bob VASP", + "legalPersonNameIdentifierType": "SHRT" + } + ] + }, + "geographicAddresses": [{ + "addressType": "BIZZ", + "streetName": "Grimsby Road", + "buildingNumber": "762", + "postCode": "OX8 U89", + "townName": "Oxford", + "country": "GB" + }], + "nationalIdentification": { + "nationalIdentifier": "213800AQUAUP6I215N33", + "nationalIdentifierType": "LEIX", + "country_of_issue": "GB", + "registration_authority": "RA000589" + }, + "countryOfRegistration": "GB" + } + }, + "transferPath": { + "transferPath": [] + }, + "payloadMetadata": { + "transliterationMethod": [] + } +} \ No newline at end of file diff --git a/pkg/trisa/api/v1beta1/api.pb.go b/pkg/trisa/api/v1beta1/api.pb.go index be7421a..71e8fd2 100644 --- a/pkg/trisa/api/v1beta1/api.pb.go +++ b/pkg/trisa/api/v1beta1/api.pb.go @@ -276,7 +276,7 @@ type Payload struct { // facilitate multi-message compliance exchanges. These messages must all be // digtially signed for auditing purposes. Transaction *anypb.Any `protobuf:"bytes,2,opt,name=transaction,proto3" json:"transaction,omitempty"` - // Timestamps the describe when the payload was originally sent and when it was + // Timestamps that describe when the payload was originally sent and when it was // accepted or received by the counterparty. These timestamps must be in the payload // so that they are digitally signed for non-repudiation. Both timestamps should be // RFC-3339 formatted strings with timezone information. diff --git a/pkg/trisa/api/v1beta1/errors.go b/pkg/trisa/api/v1beta1/errors.go index 3be9aac..90bf9ab 100644 --- a/pkg/trisa/api/v1beta1/errors.go +++ b/pkg/trisa/api/v1beta1/errors.go @@ -125,11 +125,17 @@ func (e *Error) WithDetails(details proto.Message) (_ *Error, err error) { // Error implements the error interface for printing and logging. func (e *Error) Error() string { - return fmt.Sprintf("trisa error [%s]: %s", e.Code, e.Message) + return fmt.Sprintf("trisa rejection [%s]: %s", e.Code, e.Message) +} + +// IsZero returns true if the error has a code == 0 and no message +func (e *Error) IsZero() bool { + return e.Code == 0 && e.Message == "" } // Err returns a gRPC status error with appropriate gRPC status codes for returning // out of a gRPC server function. This should be returned where possible. +// Deprecated: return an error inside of the envelope for a rejection. func (e *Error) Err() (err error) { var code codes.Code switch { diff --git a/pkg/trisa/api/v1beta1/errors.pb.go b/pkg/trisa/api/v1beta1/errors.pb.go index 0bec9b2..1bb165e 100644 --- a/pkg/trisa/api/v1beta1/errors.pb.go +++ b/pkg/trisa/api/v1beta1/errors.pb.go @@ -44,7 +44,8 @@ const ( // VASP does not control the specified wallet address. Error_UNKNOWN_WALLET_ADDRESS Error_Code = 51 // VASP does not have KYC information for the specified wallet address. - Error_UNKOWN_IDENTITY Error_Code = 52 + Error_UNKNOWN_IDENTITY Error_Code = 52 + Error_UNKOWN_IDENTITY Error_Code = 52 // Typo left for backwards compatibility. // Specifically, the Originator account cannot be identified. Error_UNKNOWN_ORIGINATOR Error_Code = 53 // Specifically, the Beneficiary account cannot be identified. @@ -119,7 +120,8 @@ var ( // Duplicate value: 49: "BVRC999", 50: "REJECTED", 51: "UNKNOWN_WALLET_ADDRESS", - 52: "UNKOWN_IDENTITY", + 52: "UNKNOWN_IDENTITY", + // Duplicate value: 52: "UNKOWN_IDENTITY", 53: "UNKNOWN_ORIGINATOR", 54: "UNKOWN_BENEFICIARY", // Duplicate value: 54: "BENEFICIARY_NAME_UNMATCHED", @@ -164,6 +166,7 @@ var ( "BVRC999": 49, "REJECTED": 50, "UNKNOWN_WALLET_ADDRESS": 51, + "UNKNOWN_IDENTITY": 52, "UNKOWN_IDENTITY": 52, "UNKNOWN_ORIGINATOR": 53, "UNKOWN_BENEFICIARY": 54, @@ -315,7 +318,7 @@ var file_trisa_api_v1beta1_errors_proto_rawDesc = []byte{ 0x74, 0x61, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x72, 0x69, 0x73, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9b, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x08, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x73, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, @@ -325,7 +328,7 @@ var file_trisa_api_v1beta1_errors_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, 0x74, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, - 0x6e, 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xfe, 0x06, 0x0a, 0x04, + 0x6e, 0x79, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x94, 0x07, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, @@ -337,55 +340,57 @@ var file_trisa_api_v1beta1_errors_proto_rawDesc = []byte{ 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x39, 0x39, 0x39, 0x10, 0x31, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x32, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x41, 0x4c, 0x4c, 0x45, 0x54, 0x5f, 0x41, 0x44, - 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x33, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4f, 0x57, - 0x4e, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x34, 0x12, 0x16, 0x0a, 0x12, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x41, 0x54, - 0x4f, 0x52, 0x10, 0x35, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x4e, 0x4b, 0x4f, 0x57, 0x4e, 0x5f, 0x42, - 0x45, 0x4e, 0x45, 0x46, 0x49, 0x43, 0x49, 0x41, 0x52, 0x59, 0x10, 0x36, 0x12, 0x1e, 0x0a, 0x1a, - 0x42, 0x45, 0x4e, 0x45, 0x46, 0x49, 0x43, 0x49, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x41, 0x4d, 0x45, - 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x45, 0x44, 0x10, 0x36, 0x12, 0x0b, 0x0a, 0x07, - 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x37, 0x10, 0x36, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x4e, 0x53, - 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, - 0x59, 0x10, 0x3c, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x31, 0x10, 0x3c, - 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x5f, 0x54, 0x52, 0x41, - 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x56, 0x4f, 0x4c, 0x55, 0x4d, 0x45, 0x10, 0x3d, 0x12, 0x0b, 0x0a, - 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x33, 0x10, 0x3d, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, - 0x4d, 0x50, 0x4c, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x10, 0x5a, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x34, - 0x10, 0x5a, 0x12, 0x11, 0x0a, 0x0d, 0x4e, 0x4f, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x49, 0x41, - 0x4e, 0x43, 0x45, 0x10, 0x5b, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x49, 0x47, 0x48, 0x5f, 0x52, 0x49, - 0x53, 0x4b, 0x10, 0x5c, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x55, 0x54, 0x5f, 0x4f, 0x46, 0x5f, 0x4e, - 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x63, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, - 0x49, 0x44, 0x44, 0x45, 0x4e, 0x10, 0x64, 0x12, 0x12, 0x0a, 0x0e, 0x4e, 0x4f, 0x5f, 0x53, 0x49, - 0x47, 0x4e, 0x49, 0x4e, 0x47, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x43, - 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, - 0x45, 0x44, 0x10, 0x66, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x67, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x54, 0x52, 0x55, 0x53, 0x54, 0x45, - 0x44, 0x10, 0x68, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x53, - 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x10, 0x69, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, - 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x6a, 0x12, 0x18, 0x0a, 0x14, 0x45, - 0x4e, 0x56, 0x45, 0x4c, 0x4f, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x10, 0x6b, 0x12, 0x1c, 0x0a, 0x18, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, - 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, - 0x4c, 0x10, 0x6b, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x35, 0x10, 0x6b, - 0x12, 0x17, 0x0a, 0x13, 0x55, 0x4e, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x44, 0x5f, 0x41, 0x4c, - 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x10, 0x6c, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x41, 0x44, - 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x96, 0x01, 0x12, 0x19, 0x0a, 0x14, 0x55, - 0x4e, 0x50, 0x41, 0x52, 0x53, 0x45, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, - 0x49, 0x54, 0x59, 0x10, 0x97, 0x01, 0x12, 0x1e, 0x0a, 0x19, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, - 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, - 0x4d, 0x41, 0x54, 0x10, 0x97, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, - 0x36, 0x10, 0x97, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x55, 0x4e, 0x50, 0x41, 0x52, 0x53, 0x45, 0x41, - 0x42, 0x4c, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, - 0x98, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, - 0x45, 0x4c, 0x44, 0x53, 0x10, 0x99, 0x01, 0x12, 0x18, 0x0a, 0x13, 0x49, 0x4e, 0x43, 0x4f, 0x4d, - 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x9a, - 0x01, 0x12, 0x15, 0x0a, 0x10, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x9b, 0x01, 0x1a, 0x02, 0x10, 0x01, 0x42, 0x38, 0x5a, 0x36, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x69, 0x73, 0x61, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x69, 0x73, 0x61, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x74, 0x72, 0x69, 0x73, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x33, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x34, 0x12, 0x13, 0x0a, + 0x0f, 0x55, 0x4e, 0x4b, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, + 0x10, 0x34, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4f, 0x52, + 0x49, 0x47, 0x49, 0x4e, 0x41, 0x54, 0x4f, 0x52, 0x10, 0x35, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x4e, + 0x4b, 0x4f, 0x57, 0x4e, 0x5f, 0x42, 0x45, 0x4e, 0x45, 0x46, 0x49, 0x43, 0x49, 0x41, 0x52, 0x59, + 0x10, 0x36, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x45, 0x4e, 0x45, 0x46, 0x49, 0x43, 0x49, 0x41, 0x52, + 0x59, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x45, 0x44, + 0x10, 0x36, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x37, 0x10, 0x36, 0x12, + 0x18, 0x0a, 0x14, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x43, + 0x55, 0x52, 0x52, 0x45, 0x4e, 0x43, 0x59, 0x10, 0x3c, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, + 0x43, 0x30, 0x30, 0x31, 0x10, 0x3c, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, + 0x45, 0x44, 0x5f, 0x54, 0x52, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x56, 0x4f, 0x4c, 0x55, 0x4d, + 0x45, 0x10, 0x3d, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x33, 0x10, 0x3d, + 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x43, + 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x5a, 0x12, 0x0b, 0x0a, 0x07, 0x42, + 0x56, 0x52, 0x43, 0x30, 0x30, 0x34, 0x10, 0x5a, 0x12, 0x11, 0x0a, 0x0d, 0x4e, 0x4f, 0x5f, 0x43, + 0x4f, 0x4d, 0x50, 0x4c, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x5b, 0x12, 0x0d, 0x0a, 0x09, 0x48, + 0x49, 0x47, 0x48, 0x5f, 0x52, 0x49, 0x53, 0x4b, 0x10, 0x5c, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x55, + 0x54, 0x5f, 0x4f, 0x46, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x10, 0x63, 0x12, 0x0d, + 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, 0x10, 0x64, 0x12, 0x12, 0x0a, + 0x0e, 0x4e, 0x4f, 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x49, 0x4e, 0x47, 0x5f, 0x4b, 0x45, 0x59, 0x10, + 0x65, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, 0x66, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, + 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x67, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, + 0x54, 0x52, 0x55, 0x53, 0x54, 0x45, 0x44, 0x10, 0x68, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, + 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x10, 0x69, + 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x10, + 0x6a, 0x12, 0x18, 0x0a, 0x14, 0x45, 0x4e, 0x56, 0x45, 0x4c, 0x4f, 0x50, 0x45, 0x5f, 0x44, 0x45, + 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x6b, 0x12, 0x1c, 0x0a, 0x18, 0x50, + 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x44, 0x45, 0x43, 0x4f, + 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x6b, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x56, 0x52, + 0x43, 0x30, 0x30, 0x35, 0x10, 0x6b, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x4e, 0x48, 0x41, 0x4e, 0x44, + 0x4c, 0x45, 0x44, 0x5f, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x10, 0x6c, 0x12, + 0x10, 0x0a, 0x0b, 0x42, 0x41, 0x44, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x96, + 0x01, 0x12, 0x19, 0x0a, 0x14, 0x55, 0x4e, 0x50, 0x41, 0x52, 0x53, 0x45, 0x41, 0x42, 0x4c, 0x45, + 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x97, 0x01, 0x12, 0x1e, 0x0a, 0x19, + 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x57, 0x52, 0x4f, + 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x10, 0x97, 0x01, 0x12, 0x0c, 0x0a, 0x07, + 0x42, 0x56, 0x52, 0x43, 0x30, 0x30, 0x36, 0x10, 0x97, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x55, 0x4e, + 0x50, 0x41, 0x52, 0x53, 0x45, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x41, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x98, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x4d, 0x49, 0x53, 0x53, + 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x53, 0x10, 0x99, 0x01, 0x12, 0x18, 0x0a, + 0x13, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x49, 0x44, 0x45, 0x4e, + 0x54, 0x49, 0x54, 0x59, 0x10, 0x9a, 0x01, 0x12, 0x15, 0x0a, 0x10, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x9b, 0x01, 0x1a, 0x02, + 0x10, 0x01, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x74, 0x72, 0x69, 0x73, 0x61, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x69, + 0x73, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x72, 0x69, 0x73, 0x61, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/trisa/api/v1beta1/errors_test.go b/pkg/trisa/api/v1beta1/errors_test.go index 00e298f..e9e662c 100644 --- a/pkg/trisa/api/v1beta1/errors_test.go +++ b/pkg/trisa/api/v1beta1/errors_test.go @@ -15,22 +15,26 @@ import ( func TestErrors(t *testing.T) { err := api.Errorf(api.UnknownIdentity, "could not parse %q", "foo") require.Error(t, err) - require.Equal(t, err.Error(), `trisa error [UNKOWN_IDENTITY]: could not parse "foo"`) + require.Equal(t, err.Error(), `trisa rejection [UNKNOWN_IDENTITY]: could not parse "foo"`) + require.False(t, err.IsZero()) oerr, ok := api.Errorp(err) require.True(t, ok) require.Equal(t, err, oerr) + require.False(t, oerr.IsZero()) oerr, ok = api.Errorp(errors.New("unhandled error")) require.False(t, ok) - require.Equal(t, oerr.Error(), "trisa error [UNHANDLED]: unhandled error") + require.Equal(t, oerr.Error(), "trisa rejection [UNHANDLED]: unhandled error") + require.False(t, oerr.IsZero()) sterr := err.Err() - require.Equal(t, sterr.Error(), `rpc error: code = Aborted desc = [UNKOWN_IDENTITY] could not parse "foo"`) + require.Equal(t, sterr.Error(), `rpc error: code = Aborted desc = [UNKNOWN_IDENTITY] could not parse "foo"`) oerr, ok = api.Errorp(sterr) require.True(t, ok) require.True(t, proto.Equal(err, oerr), "unexpected return value from Errorp") + require.False(t, oerr.IsZero()) // WithRetry should return a new error with retry set to true errWithRetry := err.WithRetry() @@ -38,6 +42,7 @@ func TestErrors(t *testing.T) { require.Equal(t, err.Message, errWithRetry.Message) require.True(t, errWithRetry.Retry) require.Nil(t, errWithRetry.Details) + require.False(t, errWithRetry.IsZero()) _, parseErr := err.WithDetails(nil) require.Error(t, parseErr) @@ -54,6 +59,35 @@ func TestErrors(t *testing.T) { actualDetails := &api.Error{} require.NoError(t, anypb.UnmarshalTo(errWithDetails.Details, actualDetails, proto.UnmarshalOptions{})) require.True(t, proto.Equal(details, actualDetails), "unexpected details created by WithDetails") + require.False(t, errWithDetails.IsZero()) +} + +func TestIsZero(t *testing.T) { + err := &api.Error{} + require.True(t, err.IsZero(), "no code and no message should be zero valued") + + err = &api.Error{Retry: true} + require.True(t, err.IsZero(), "non-zero retry is not sufficient") + + details, _ := anypb.New(&api.Error{Code: api.ExceededTradingVolume, Message: "too fast"}) + err = &api.Error{Details: details} + require.True(t, err.IsZero(), "non-zero details is not sufficient") + + err = &api.Error{Code: api.OutOfNetwork} + require.False(t, err.IsZero(), "a code greater than zero should be sufficient") + + err = &api.Error{Message: "unexpected content"} + require.False(t, err.IsZero(), "a message without a code should be sufficient") + + err = &api.Error{Code: api.OutOfNetwork, Message: "unexpected content"} + require.False(t, err.IsZero(), "both a message and a code should be non-zero") + + // After marshaling an empty protocol buffer message, it should still be zero + data, merr := proto.Marshal(&api.Error{}) + require.NoError(t, merr, "could not marshal protocol buffer") + umerr := &api.Error{} + require.NoError(t, proto.Unmarshal(data, umerr), "could not unmarshal error pb") + require.True(t, umerr.IsZero(), "should be zero after marshal and unmarshal") } // Test that the Err function returns an error that includes the corresponding gRPC diff --git a/pkg/trisa/crypto/crypto.go b/pkg/trisa/crypto/crypto.go index d6f7154..00a9b8b 100644 --- a/pkg/trisa/crypto/crypto.go +++ b/pkg/trisa/crypto/crypto.go @@ -14,6 +14,7 @@ import "crypto/rand" type Crypto interface { Cipher Signer + KeyHandler } // Cipher is a device that can perform encryption and decryption, This interface wraps @@ -32,6 +33,17 @@ type Signer interface { SignatureAlgorithm() string } +// KeyHandler can return its internal encryption key and hmac secret +type KeyHandler interface { + EncryptionKey() (key []byte) + HMACSecret() (secret []byte) +} + +// KeyIdentifier can return a hash value or some other identifying material for a public key +type KeyIdentifier interface { + PublicKeySignature() (string, error) +} + // Random generates a secure random sequence of bytes, this helper function is used to // easily create keys, salts, and secrets in the crypto subpackages. func Random(n int) (b []byte, err error) { diff --git a/pkg/trisa/crypto/rsaoeap/rsaoeap.go b/pkg/trisa/crypto/rsaoeap/rsaoeap.go index 1a14509..1a11ec7 100644 --- a/pkg/trisa/crypto/rsaoeap/rsaoeap.go +++ b/pkg/trisa/crypto/rsaoeap/rsaoeap.go @@ -3,7 +3,10 @@ package rsaoeap import ( "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/sha512" + "crypto/x509" + "encoding/base64" "errors" "fmt" ) @@ -60,3 +63,18 @@ func (c *RSA) Decrypt(ciphertext []byte) (plaintext []byte, err error) { func (c *RSA) EncryptionAlgorithm() string { return "RSA-OAEP-SHA512" } + +// PublicKeySignature implements KeyIdentifier by computing a base64 encoded SHA-256 +// hash of the public key serialized as a PKIX public key without PEM encoding. This is +// a prototype method of computing the public key signature and may not match other +// external signature computation methods. +// TODO: verify that this method matches openssl or GitHub public key identification. +func (c *RSA) PublicKeySignature() (_ string, err error) { + var data []byte + if data, err = x509.MarshalPKIXPublicKey(c.pub); err != nil { + return "", err + } + + sum := sha256.Sum256(data) + return fmt.Sprintf("SHA256:%s", base64.RawStdEncoding.EncodeToString(sum[:])), nil +} diff --git a/pkg/trisa/envelope/envelope.go b/pkg/trisa/envelope/envelope.go new file mode 100644 index 0000000..cf491b8 --- /dev/null +++ b/pkg/trisa/envelope/envelope.go @@ -0,0 +1,756 @@ +/* +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. +*/ +package envelope + +import ( + "fmt" + "time" + + "github.com/google/uuid" + api "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1" + "github.com/trisacrypto/trisa/pkg/trisa/crypto" + "github.com/trisacrypto/trisa/pkg/trisa/crypto/aesgcm" + "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 + if err = env.ValidateMessage(); err != nil { + return nil, err + } + + return env.Proto(), nil +} + +// 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. +type Envelope struct { + msg *api.SecureEnvelope + payload *api.Payload + crypto crypto.Crypto + seal crypto.Cipher +} + +//=========================================================================== +// Envelope Constructors +//=========================================================================== + +// 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 New(payload *api.Payload, opts ...Option) (env *Envelope, err error) { + // Create a new empty secure envelope + env = &Envelope{ + msg: &api.SecureEnvelope{ + Id: uuid.NewString(), + Payload: nil, + EncryptionKey: nil, + EncryptionAlgorithm: "", + Hmac: nil, + HmacSecret: nil, + HmacAlgorithm: "", + Error: nil, + Timestamp: time.Now().Format(time.RFC3339Nano), + Sealed: false, + PublicKeySignature: "", + }, + payload: payload, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, err + } + } + return env, nil +} + +// 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 Wrap(msg *api.SecureEnvelope, opts ...Option) (env *Envelope, err error) { + if msg == nil { + return nil, ErrNoMessage + } + + // Wrap the secure envelope with the envelope handler. + env = &Envelope{ + msg: msg, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, err + } + } + return env, nil +} + +//=========================================================================== +// Envelope State Transitions +//=========================================================================== + +// 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 (e *Envelope) Reject(reject *api.Error, opts ...Option) (env *Envelope, err error) { + env = &Envelope{ + msg: &api.SecureEnvelope{ + Id: e.msg.Id, + Payload: nil, + EncryptionKey: nil, + EncryptionAlgorithm: "", + Hmac: nil, + HmacSecret: nil, + HmacAlgorithm: "", + Error: reject, + Timestamp: time.Now().Format(time.RFC3339Nano), + Sealed: false, + PublicKeySignature: "", + }, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, err + } + } + return env, nil +} + +// 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 (e *Envelope) Encrypt(opts ...Option) (env *Envelope, reject *api.Error, err error) { + // The envelope may only be encrypted from the Clear state (e.g. it has a payload) + state := e.State() + if state != Clear && state != ClearError { + return nil, nil, fmt.Errorf("cannot encrypt envelope from %q state", state) + } + + // Clone the envelope + env = &Envelope{ + msg: &api.SecureEnvelope{ + Id: e.msg.Id, + Payload: nil, + EncryptionKey: nil, + EncryptionAlgorithm: "", + Hmac: nil, + HmacSecret: nil, + HmacAlgorithm: "", + Error: e.msg.Error, + Timestamp: time.Now().Format(time.RFC3339Nano), + Sealed: false, + PublicKeySignature: "", + }, + crypto: e.crypto, + seal: e.seal, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, nil, err + } + } + + // Validate the payload before encrypting + if err = e.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 + } + } + + // Encrypt the payload and fill in the details on the envelope. + if reject, err = env.encrypt(e.payload); err != nil { + return nil, reject, err + } + return env, reject, nil +} + +// Internal encrypt method to update an envelope directly and for short-circuit methods. +func (e *Envelope) encrypt(payload *api.Payload) (_ *api.Error, err error) { + // TODO: should we inspect the envelope for encryption metadata to create the cipher? + if e.crypto == nil { + return nil, ErrCannotEncrypt + } + + var cleartext []byte + if cleartext, err = proto.Marshal(e.payload); err != nil { + return nil, fmt.Errorf("could not marshal payload: %s", err) + } + + if e.msg.Payload, err = e.crypto.Encrypt(cleartext); err != nil { + return nil, fmt.Errorf("could not encrypt payload data: %s", err) + } + + if e.msg.Hmac, err = e.crypto.Sign(e.msg.Payload); err != nil { + return nil, fmt.Errorf("could not sign payload data: %s", err) + } + + // Populate metadata on envelope + e.msg.EncryptionKey = e.crypto.EncryptionKey() + e.msg.HmacSecret = e.crypto.HMACSecret() + e.msg.EncryptionAlgorithm = e.crypto.EncryptionAlgorithm() + e.msg.HmacAlgorithm = e.crypto.SignatureAlgorithm() + e.msg.Sealed = false + e.msg.PublicKeySignature = "" + + // Validate the message before returning + if err = e.ValidateMessage(); err != nil { + return nil, err + } + return nil, nil +} + +// The original envelope is not modified, the secure envelope is cloned. +func (e *Envelope) Decrypt(opts ...Option) (env *Envelope, reject *api.Error, err error) { + // The envelope may only be decrypted from the Unsealed state (e.g. the encryption key is in the clear) + state := e.State() + if state != Unsealed && state != UnsealedError { + return nil, nil, fmt.Errorf("cannot decrypt envelope from %q state", state) + } + + // Clone the envelope + env = &Envelope{ + msg: &api.SecureEnvelope{ + Id: e.msg.Id, + Payload: e.msg.Payload, + EncryptionKey: e.msg.EncryptionKey, + EncryptionAlgorithm: e.msg.EncryptionAlgorithm, + Hmac: e.msg.Hmac, + HmacSecret: e.msg.HmacSecret, + HmacAlgorithm: e.msg.HmacAlgorithm, + Error: e.msg.Error, + Timestamp: time.Now().Format(time.RFC3339Nano), + Sealed: false, + PublicKeySignature: "", + }, + crypto: e.crypto, + seal: e.seal, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, nil, err + } + } + + // Decrypt the payload and update the details on the envelope + if reject, err = env.decrypt(); err != nil { + return nil, reject, err + } + return env, reject, nil +} + +// Internal decrypt method to update an envelope directly and for short-circuit methods. +func (e *Envelope) decrypt() (_ *api.Error, err error) { + // Validate the message contains the required data to decrypt + if err = e.ValidateMessage(); err != nil { + return nil, err + } + + if e.crypto == nil { + // Create the cipher from the data on the envelope + // Check if the encryption algorithms are supported + // TODO: allow more algorithms by adding composition functionality to the crypto pacakge + if e.msg.EncryptionAlgorithm != "AES256-GCM" { + err = fmt.Errorf("unsupported encryption algorithm %q", e.msg.EncryptionAlgorithm) + return api.Errorf(api.UnhandledAlgorithm, err.Error()), err + } + if e.msg.HmacAlgorithm != "HMAC-SHA256" { + err = fmt.Errorf("unsupported digital signature algorithm %q", e.msg.HmacAlgorithm) + return api.Errorf(api.UnhandledAlgorithm, err.Error()), err + } + + if e.crypto, err = aesgcm.New(e.msg.EncryptionKey, e.msg.HmacSecret); err != nil { + return nil, fmt.Errorf("could not create AES-GCM cipher for payload decryption: %v", err) + } + } + + // Verify the HMAC signature of the envelope + if err = e.crypto.Verify(e.msg.Payload, e.msg.Hmac); err != nil { + return api.Errorf(api.InvalidSignature, "could not verify HMAC signature"), err + } + + // Decrypt the payload data + var data []byte + if data, err = e.crypto.Decrypt(e.msg.Payload); err != nil { + return api.Errorf(api.InvalidKey, "could not decrypt payload with encryption key"), err + } + + // Parse the payload + e.payload = &api.Payload{} + if err = proto.Unmarshal(data, e.payload); err != nil { + return api.Errorf(api.EnvelopeDecodeFail, "could not unmarshal payload from decrypted data"), err + } + + // Validate the payload + // TODO: use more specific error such as UNPARSEABLE_TRANSACTION or INCOMPLETE_IDENTITY + if err = e.ValidatePayload(); err != nil { + return api.Errorf(api.ValidationError, err.Error()), err + } + + // Set the payload and the signature to nil now that the message is in clear text + e.msg.Payload = nil + e.msg.Hmac = nil + return nil, nil +} + +// 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 (e *Envelope) Seal(opts ...Option) (env *Envelope, reject *api.Error, err error) { + // The envelope may only be sealed from the Unsealed state (e.g. it has been encrypted) + state := e.State() + if state != Unsealed && state != UnsealedError { + return nil, nil, fmt.Errorf("cannot seal envelope from %q state", state) + } + + // Clone the envelope + env = &Envelope{ + msg: &api.SecureEnvelope{ + Id: e.msg.Id, + Payload: e.msg.Payload, + EncryptionKey: e.msg.EncryptionKey, + EncryptionAlgorithm: e.msg.EncryptionAlgorithm, + Hmac: e.msg.Hmac, + HmacSecret: e.msg.HmacSecret, + HmacAlgorithm: e.msg.HmacAlgorithm, + Error: e.msg.Error, + Timestamp: time.Now().Format(time.RFC3339Nano), + Sealed: false, + PublicKeySignature: "", + }, + crypto: e.crypto, + seal: e.seal, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, nil, err + } + } + + // Seal the payload and fill in the details on the envelope. + if reject, err = env.sealEnvelope(); err != nil { + return nil, reject, err + } + return env, reject, nil +} + +// Internal seal envelope method to update an envelope directly and for short-circuit methods. +func (e *Envelope) sealEnvelope() (_ *api.Error, err error) { + if e.seal == nil { + return nil, ErrCannotSeal + } + + if e.msg.EncryptionKey, err = e.seal.Encrypt(e.msg.EncryptionKey); err != nil { + return nil, fmt.Errorf("could not seal encryption key: %s", err) + } + + if e.msg.HmacSecret, err = e.seal.Encrypt(e.msg.HmacSecret); err != nil { + return nil, fmt.Errorf("could not seal hmac secret: %s", err) + } + + // Message is now sealed + e.msg.Sealed = true + + // Add public key signature if the key supports it + if pks, ok := e.seal.(crypto.KeyIdentifier); ok { + if e.msg.PublicKeySignature, err = pks.PublicKeySignature(); err != nil { + return nil, fmt.Errorf("could not compute public key signature: %s", err) + } + } + return nil, nil +} + +// 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 (e *Envelope) Unseal(opts ...Option) (env *Envelope, reject *api.Error, err error) { + // The envelope may only be unsealed from the Sealed state (e.g. it is a valid incoming secure envelope) + state := e.State() + if state != Sealed && state != SealedError { + return nil, nil, fmt.Errorf("cannot seal envelope from %q state", state) + } + + // Clone the envelope + env = &Envelope{ + msg: &api.SecureEnvelope{ + Id: e.msg.Id, + Payload: e.msg.Payload, + EncryptionKey: e.msg.EncryptionKey, + EncryptionAlgorithm: e.msg.EncryptionAlgorithm, + Hmac: e.msg.Hmac, + HmacSecret: e.msg.HmacSecret, + HmacAlgorithm: e.msg.HmacAlgorithm, + Error: e.msg.Error, + Timestamp: time.Now().Format(time.RFC3339Nano), + Sealed: e.msg.Sealed, + PublicKeySignature: e.msg.PublicKeySignature, + }, + crypto: e.crypto, + seal: e.seal, + } + + // Apply the options + for _, opt := range opts { + if err = opt(env); err != nil { + return nil, nil, err + } + } + + // Seal the payload and fill in the details on the envelope. + if reject, err = env.unsealEnvelope(); err != nil { + return nil, reject, err + } + return env, reject, nil +} + +// Internal unseal envelope method to update envelope directly and for short-circuit methods. +func (e *Envelope) unsealEnvelope() (reject *api.Error, err error) { + if e.seal == nil { + return nil, ErrCannotUnseal + } + + if e.msg.EncryptionKey, err = e.seal.Decrypt(e.msg.EncryptionKey); err != nil { + return api.Errorf(api.InvalidKey, "could not unseal encryption key").WithRetry(), err + } + + if e.msg.HmacSecret, err = e.seal.Decrypt(e.msg.HmacSecret); err != nil { + return api.Errorf(api.InvalidKey, "could not unseal HMAC secret").WithRetry(), err + } + + // Mark the envelope as unsealed + e.msg.Sealed = false + e.msg.PublicKeySignature = "" + return nil, nil +} + +//=========================================================================== +// Envelope Accessors +//=========================================================================== + +// ID returns the envelope ID +func (e *Envelope) ID() string { + return e.msg.Id +} + +// Proto returns the trisa.SecureEnvelope protocol buffer. +func (e *Envelope) Proto() *api.SecureEnvelope { + return e.msg +} + +// Payload returns the parsed trisa.Payload protocol buffer if available. If the +// envelope is not decrypted then an error is returned. +func (e *Envelope) Payload() (_ *api.Payload, err error) { + state := e.State() + if state != Clear && state != ClearError { + err = fmt.Errorf("envelope is in state %q: payload may be invalid", state) + } + return e.payload, err +} + +// Error returns the TRISA rejection error on the envelope if it exists +func (e *Envelope) Error() *api.Error { + // Ensure a nil error is returned if the error is zero-valued + if e.msg.Error != nil && e.msg.Error.IsZero() { + return nil + } + return e.msg.Error +} + +// 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 (e *Envelope) Timestamp() (ts time.Time, err error) { + if e.msg.Timestamp == "" { + return ts, &api.Error{Code: api.BadRequest, Message: "missing ordering timestamp on secure envelope"} + } + + // First attempt: parse with nanosecond resolution + if ts, err = time.Parse(time.RFC3339Nano, e.msg.Timestamp); err != nil { + // Second attempt: try without nanosecond resolution + if ts, err = time.Parse(time.RFC3339, e.msg.Timestamp); err != nil { + return ts, &api.Error{Code: api.BadRequest, Message: "could not parse ordering timestamp on secure envelope as RFC3339 timestamp"} + } + } + return ts, err +} + +// Crypto returns the cryptographic method used to encrypt/decrypt the payload. +func (e *Envelope) Crypto() crypto.Crypto { + return e.crypto +} + +// Sealer returns the cryptographic cipher method used to seal/unseal the envelope. +func (e *Envelope) Sealer() crypto.Cipher { + return e.seal +} + +//=========================================================================== +// Envelope Validation +//=========================================================================== + +// State returns the current state of the envelope. +func (e *Envelope) State() State { + // If a payload exists on the envelope, then it is in the clear + if e.payload != nil { + if e.msg.Error == nil || e.msg.Error.IsZero() { + return Clear + } + return ClearError + } + + // If there is no payload, there should be a secure envelope + if e.msg == nil { + return Unknown + } + + // If there is a secure envelope, it should be valid + if err := e.ValidateMessage(); err != nil { + return Corrupted + } + + // Check if the envelope is marked as sealed + if e.msg.Sealed { + if e.msg.Error == nil || e.msg.Error.IsZero() { + return Sealed + } + return SealedError + } + + // Message is unsealed + if e.msg.Error == nil || e.msg.Error.IsZero() { + return Unsealed + } + return UnsealedError +} + +// ValidateMessage returns an error if the secure envelope does not have the required fields to send. +func (e *Envelope) ValidateMessage() error { + if e.msg == nil { + return ErrNoMessage + } + + if e.msg.Id == "" { + return ErrNoEnvelopeId + } + + if e.msg.Timestamp == "" { + return ErrNoTimestamp + } + + // The message should have either an error or an encrypted payload + if len(e.msg.Payload) == 0 { + if e.msg.Error == nil || e.msg.Error.IsZero() { + return ErrNoMessageData + } + return nil + } + + // If there is a payload then all payload fields should be set + if len(e.msg.EncryptionKey) == 0 || e.msg.EncryptionAlgorithm == "" { + return ErrNoEncryptionInfo + } + + if len(e.msg.Hmac) == 0 || len(e.msg.HmacSecret) == 0 || e.msg.HmacAlgorithm == "" { + return ErrNoHMACInfo + } + + // Note: not validating public_key_signature or sealed fields + return nil +} + +// 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? +func (e *Envelope) ValidatePayload() error { + if e.payload == nil { + return ErrNoPayload + } + + // Identity payload is required + if e.payload.Identity == nil { + return ErrNoIdentityPayload + } + + // A transaction is required + if e.payload.Transaction == nil { + return ErrNoTransactionPayload + } + + // The SentAt timestamp is required and should be parseable + if e.payload.SentAt == "" { + return ErrNoSentAtPayload + } + + if _, err := time.Parse(time.RFC3339, e.payload.SentAt); err != nil { + return ErrInvalidSentAtPayload + } + + // If the ReceivedAt timestamp is available, it should be parseable + if e.payload.ReceivedAt != "" { + if _, err := time.Parse(time.RFC3339, e.payload.ReceivedAt); err != nil { + return ErrInvalidReceivedatPayload + } + } + + return nil +} diff --git a/pkg/trisa/envelope/envelope_test.go b/pkg/trisa/envelope/envelope_test.go new file mode 100644 index 0000000..3cbfccb --- /dev/null +++ b/pkg/trisa/envelope/envelope_test.go @@ -0,0 +1,587 @@ +package envelope_test + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + "log" + "os" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "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 ExampleSeal() { + // 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) +} + +func Example_create() { + // 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) +} + +func ExampleOpen() { + // 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) +} + +func Example_parse() { + // 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) +} + +// Test the creation of an envelope from scratch, moving it through each state. +func TestSendEnvelopeWorkflow(t *testing.T) { + payload, err := loadPayloadFixture("testdata/payload.json") + require.NoError(t, err, "could not load payload") + + key, err := loadPrivateKey("testdata/sealing_key.pem") + require.NoError(t, err, "could not load sealing key") + + env, err := envelope.New(payload, envelope.WithRSAPublicKey(&key.PublicKey)) + require.NoError(t, err, "could not create envelope with no payload and no options") + require.Equal(t, envelope.Clear, env.State(), "expected clear state not %q", env.State()) + + eenv, reject, err := env.Encrypt() + require.NoError(t, err, "could not encrypt envelope") + require.Nil(t, reject, "expected no API error returned from encryption") + require.NotSame(t, env, eenv, "Encrypt should return a clone of the original envelope") + require.Equal(t, envelope.Unsealed, eenv.State(), "expected unsealed state not %q", eenv.State()) + + senv, reject, err := eenv.Seal() + require.NoError(t, err, "could not seal envelope") + require.Nil(t, reject, "expected no API error returned from sealing") + require.NotSame(t, eenv, senv, "Seal should return a clone of the original envelope") + require.Equal(t, envelope.Sealed, senv.State(), "expected sealed state not %q", senv.State()) + + // Fetch the message and check that it is ready to send + msg := senv.Proto() + require.NotEmpty(t, msg.Id, "message is missing an envelope ID") + require.NotEmpty(t, msg.Payload, "message is missing encrypted payload") + require.NotEmpty(t, msg.EncryptionKey, "message is missing encryption key") + require.Equal(t, "AES256-GCM", msg.EncryptionAlgorithm, "unexpected encryption algorithm") + require.NotEmpty(t, msg.Hmac, "message is missing HMAC digital signature") + require.NotEmpty(t, msg.HmacSecret, "message is missing HMAC secret") + require.Equal(t, "HMAC-SHA256", msg.HmacAlgorithm, "unexpected signature algorithm") + require.Nil(t, msg.Error, "unexpected error on message") + require.NotEmpty(t, msg.Timestamp, "message is missing timestamp") + require.True(t, msg.Sealed, "message is not marked as sealed") + require.NotEmpty(t, msg.PublicKeySignature, "message is missing public key signature") + require.Equal(t, "SHA256:QhEspinUU51gK0dQGqLa56BA6xyRy5/7sN5/6GlaLZw", msg.PublicKeySignature, "unexpected public key signature") +} + +// Test the handling of a secure envelope fixture through to creating a response. +func TestRecvEnvelopeWorkflow(t *testing.T) { + msg, err := loadEnvelopeFixture("testdata/sealed_envelope.json") + require.NoError(t, err, "could not load envelope") + + key, err := loadPrivateKey("testdata/sealing_key.pem") + require.NoError(t, err, "could not load sealing key") + + // Wrap the envelope ensuring it's in the sealed state + senv, err := envelope.Wrap(msg, envelope.WithRSAPrivateKey(key)) + require.NoError(t, err, "could not wrap the envelope") + require.NoError(t, senv.ValidateMessage(), "secure envelope fixture is invalid") + require.Equal(t, envelope.Sealed, senv.State(), "expected sealed state not %q", senv.State()) + + // Unseal the envelope + eenv, reject, err := senv.Unseal() + require.NoError(t, err, "could not unseal the envelope") + require.Nil(t, reject, "a rejection error was unexpectedly returned") + require.NotSame(t, senv, eenv, "Unseal should return a clone of the original envelope") + require.Equal(t, envelope.Unsealed, eenv.State(), "expected unsealed state not %q", eenv.State()) + + // Decrypt the envelope + env, reject, err := eenv.Decrypt() + require.NoError(t, err, "could not decrypt envelope") + require.Nil(t, reject, "a rejection error was unexpectedly returned") + require.NotSame(t, eenv, env, "Decrypt should return a clone of the original envelope") + require.Equal(t, envelope.Clear, env.State(), "expected clear state not %q", eenv.State()) + require.NotNil(t, env.Crypto(), "decrypted envelopes should maintain crytpo context") + require.NotNil(t, env.Sealer(), "decrypted envelopes should maintain sealer context") + + // Get the payload from the envelope + payload, err := env.Payload() + require.NoError(t, err, "could not fetch decrypted payload from envelope") + require.NotNil(t, payload, "nil payload unexpectedly returned") + + // Load the payload fixture for verification + expectedPayload, err := loadPayloadFixture("testdata/pending_payload.json") + require.NoError(t, err, "could not load payload fixture") + require.True(t, proto.Equal(payload, expectedPayload), "decrypted payload did not match payload fixture, did fixture change?") + + // Update the payload with received at and reseal the envelope + // TODO: does this modify the payload of the original message? + payload.ReceivedAt = time.Now().Format(time.RFC3339) + + oenv, err := envelope.New(payload, envelope.FromEnvelope(env), envelope.WithRSAPublicKey(&key.PublicKey)) + require.NoError(t, err, "could not create new envelope from original envelope") + + eoenv, reject, err := oenv.Encrypt() + require.NoError(t, err, "could not encrypt envelope") + require.Nil(t, reject, "a rejection error was unexpectedly returned") + require.NotSame(t, oenv, eoenv, "envelope unexpectedly not cloned") + + soenv, reject, err := eoenv.Seal() + require.NoError(t, err, "could not encrypt envelope") + require.Nil(t, reject, "a rejection error was unexpectedly returned") + require.NotSame(t, eoenv, soenv, "envelope unexpectedly not cloned") + + out := soenv.Proto() + + // NOTE: cannot use proto.Equal since the timestamp at least will have changed + require.Equal(t, msg.Id, out.Id, "mismatched envelope ID") + require.NotEmpty(t, out.Payload, "missing updated, encrypted payload") + require.NotEmpty(t, out.EncryptionKey, "sealed envelope encryption key missing") + require.Equal(t, msg.EncryptionAlgorithm, out.EncryptionAlgorithm, "mismatched envelope encryption algorithm") + require.NotEmpty(t, out.Hmac, "missing updated HMAC signature") + require.NotEmpty(t, out.HmacSecret, "sealed envelope hmac secret missing") + require.Equal(t, msg.HmacAlgorithm, out.HmacAlgorithm, "mismatched envelope HMAC algorithm") + require.Equal(t, msg.Error, out.Error, "unexpected error on envelopes") + require.NotEmpty(t, out.Timestamp, "no timestamp on outgoing envelope") + require.True(t, out.Sealed, "out is not marked as sealed") + require.Equal(t, msg.PublicKeySignature, out.PublicKeySignature, "public key signature mismatch") +} + +func TestOneLiners(t *testing.T) { + payload, err := loadPayloadFixture("testdata/pending_payload.json") + require.NoError(t, err, "could not load pending payload") + + key, err := loadPrivateKey("testdata/sealing_key.pem") + require.NoError(t, err, "could not load sealing key") + + // Create an envelope from the payload and the key + msg, reject, err := envelope.Seal(payload, envelope.WithRSAPublicKey(&key.PublicKey)) + require.NoError(t, err, "could not seal envelope") + require.Nil(t, reject, "unexpected rejection error") + + // Ensure the msg is valid + require.NotEmpty(t, msg.Id, "no envelope id on the message") + require.NotEmpty(t, msg.Payload, "no payload on the message") + require.NotEmpty(t, msg.EncryptionKey, "no encryption key on the message") + require.NotEmpty(t, msg.EncryptionAlgorithm, "no encryption algorithm on the message") + require.NotEmpty(t, msg.Hmac, "no hmac signature on the message") + require.NotEmpty(t, msg.HmacSecret, "no hmac secret on the message") + require.NotEmpty(t, msg.HmacAlgorithm, "no hmac algorithm on the message") + require.Empty(t, msg.Error, "unexpected error on the message") + require.NotEmpty(t, msg.Timestamp, "no timestamp on the message") + require.True(t, msg.Sealed, "message not marked as sealed") + require.NotEmpty(t, msg.PublicKeySignature, "no public key signature on the message") + + // Serialize and Deserialize the message + data, err := proto.Marshal(msg) + require.NoError(t, err, "could not marshal outgoing message") + + in := &api.SecureEnvelope{} + require.NoError(t, proto.Unmarshal(data, in), "could not unmarshal incoming message") + + // Open the envelope with the private key + decryptedPayload, reject, err := envelope.Open(in, envelope.WithRSAPrivateKey(key)) + require.NoError(t, err, "could not open envelope") + require.Nil(t, reject, "unexpected rejection error") + require.True(t, proto.Equal(payload, decryptedPayload), "payloads do not match") +} + +func TestEnvelopeAccessors(t *testing.T) { + // Actual value for timestamp testing + ats := time.Now() + + // Create a secure envelope with an error + in := &api.SecureEnvelope{ + Id: uuid.NewString(), + Error: &api.Error{Code: api.ComplianceCheckFail, Message: "afraid of the dark"}, + Timestamp: ats.Format(time.RFC3339Nano), + } + + env, err := envelope.Wrap(in) + require.NoError(t, err, "could not wrap envelope") + + require.Equal(t, in.Id, env.ID(), "did not return correct envelope ID") + require.Equal(t, in, env.Proto(), "proto did not return the embedded envelope") + require.Equal(t, in.Error, env.Error(), "did not return the embedded error") + require.Nil(t, env.Crypto(), "crypto should be nil for an error-only envelope") + require.Nil(t, env.Sealer(), "seal should be nil for an error-only envelope") + + payload, err := env.Payload() + require.EqualError(t, err, `envelope is in state "unsealed-error": payload may be invalid`) + require.Nil(t, payload, "payload should be nil for an error-only envelope") + + ts, err := env.Timestamp() + require.NoError(t, err, "should have been able to parse RFC3339Nano timestamp") + require.True(t, ts.Equal(ats), "should have returned now") + + // Test parsing RFC3339 timestamp + in.Timestamp = ats.Format(time.RFC3339) + ts, err = env.Timestamp() + require.NoError(t, err, "should have been able to parse RFC3339 timestamp") + require.Less(t, ats.Sub(ts), 1*time.Second, "should have returned now without nanosecond precision") + + // Test parsing empty string timestamp + in.Timestamp = "" + _, err = env.Timestamp() + require.EqualError(t, err, "trisa rejection [BAD_REQUEST]: missing ordering timestamp on secure envelope") + + // Test parsing a bad timestamp string + in.Timestamp = "2022-15-45T38:32:12Z" + _, err = env.Timestamp() + require.EqualError(t, err, "trisa rejection [BAD_REQUEST]: could not parse ordering timestamp on secure envelope as RFC3339 timestamp") + + // Create an envelope with a payload + payload, err = loadPayloadFixture("testdata/payload.json") + require.NoError(t, err, "could not load payload fixture") + + // Create a new envelope with complete options + key, err := rsa.GenerateKey(rand.Reader, 4096) + require.NoError(t, err, "could not generate RSA key") + env, err = envelope.New(payload, envelope.WithEnvelopeID(in.Id), envelope.WithTimestamp(ts), envelope.WithAESGCM(nil, nil), envelope.WithRSAPublicKey(&key.PublicKey)) + require.NoError(t, err, "could not create new envelope from payload") + + require.Equal(t, in.Id, env.ID(), "ID did not return correct envelope ID") + require.NotNil(t, env.Proto(), "proto did not return a new secure envelope") + require.Nil(t, env.Error(), "expected no error to be on envelope") + require.NotNil(t, env.Crypto(), "crypto should not be nil") + require.NotNil(t, env.Sealer(), "seal should not be nil") + + actualPayload, err := env.Payload() + require.NoError(t, err, "error should have been returned") + require.Equal(t, payload, actualPayload, "payload should match the one instantiated") + + actualTS, err := env.Timestamp() + require.NoError(t, err, "could not fetch timestamp") + require.True(t, ts.Equal(actualTS), "timestamp did not match expected timestamp") +} + +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 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) { + // Check if the path exists, if it doesn't attempt to generate the fixture. + if check { + if _, err = os.Stat(path); os.IsNotExist(err) { + if err = generateFixtures(); err != nil { + return err + } + } + } + + var data []byte + if data, err = ioutil.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) { + // Load the components of the various payloads that will be created + 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) + } + + // Serialize the payloads + 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) + } + + // Create error-only envelope + 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) + } + + // Create unsealed envelope + 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) + } + + // Create RSA keys for sealed secure envelope fixtures + 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 ioutil.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 ioutil.WriteFile(path, block, 0600) +} + +func loadPrivateKey(path string) (key *rsa.PrivateKey, err error) { + var data []byte + if data, err = ioutil.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 +} diff --git a/pkg/trisa/envelope/errors.go b/pkg/trisa/envelope/errors.go new file mode 100644 index 0000000..e8a0a65 --- /dev/null +++ b/pkg/trisa/envelope/errors.go @@ -0,0 +1,21 @@ +package envelope + +import "errors" + +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") +) diff --git a/pkg/trisa/envelope/options.go b/pkg/trisa/envelope/options.go new file mode 100644 index 0000000..c85a057 --- /dev/null +++ b/pkg/trisa/envelope/options.go @@ -0,0 +1,94 @@ +package envelope + +import ( + "crypto/rsa" + "fmt" + "time" + + "github.com/trisacrypto/trisa/pkg/trisa/crypto" + "github.com/trisacrypto/trisa/pkg/trisa/crypto/aesgcm" + "github.com/trisacrypto/trisa/pkg/trisa/crypto/rsaoeap" +) + +type Option func(e *Envelope) error + +func FromEnvelope(env *Envelope) Option { + return func(e *Envelope) error { + e.msg.Id = env.msg.Id + e.crypto = env.crypto + return nil + } +} + +func WithEnvelopeID(id string) Option { + return func(e *Envelope) error { + e.msg.Id = id + return nil + } +} + +func WithTimestamp(ts time.Time) Option { + return func(e *Envelope) error { + e.msg.Timestamp = ts.Format(time.RFC3339Nano) + return nil + } +} + +func WithCrypto(crypto crypto.Crypto) Option { + return func(e *Envelope) error { + e.crypto = crypto + return nil + } +} + +func WithAESGCM(encryptionKey []byte, hmacSecret []byte) Option { + return func(e *Envelope) (err error) { + if e.crypto, err = aesgcm.New(encryptionKey, hmacSecret); err != nil { + return err + } + return nil + } +} + +func WithSeal(seal crypto.Cipher) Option { + return func(e *Envelope) error { + e.seal = seal + return nil + } +} + +func WithSealingKey(key interface{}) Option { + return func(e *Envelope) (err error) { + switch t := key.(type) { + case *rsa.PublicKey: + if e.seal, err = rsaoeap.New(t); err != nil { + return err + } + default: + return fmt.Errorf("could not use %T for sealing", t) + } + return nil + } +} + +func WithUnsealingKey(key interface{}) Option { + return func(e *Envelope) (err error) { + switch t := key.(type) { + case *rsa.PrivateKey: + if e.seal, err = rsaoeap.New(t); err != nil { + return err + } + default: + return fmt.Errorf("could not use %T for unsealing", t) + } + return nil + } +} + +func WithRSAPublicKey(key *rsa.PublicKey) Option { + return WithSealingKey(key) +} + +func WithRSAPrivateKey(key *rsa.PrivateKey) Option { + return WithUnsealingKey(key) +} diff --git a/pkg/trisa/envelope/state.go b/pkg/trisa/envelope/state.go new file mode 100644 index 0000000..94faf91 --- /dev/null +++ b/pkg/trisa/envelope/state.go @@ -0,0 +1,25 @@ +package envelope + +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 +) + +var stateNames = []string{"unknown", "clear", "unsealed", "sealed", "error", "clear-error", "unsealed-error", "sealed-error", "corrupted"} + +func (s State) String() string { + idx := int(s) + if idx >= len(stateNames) { + idx = 0 + } + return stateNames[idx] +} diff --git a/pkg/trisa/envelope/state_test.go b/pkg/trisa/envelope/state_test.go new file mode 100644 index 0000000..67556d5 --- /dev/null +++ b/pkg/trisa/envelope/state_test.go @@ -0,0 +1,34 @@ +package envelope_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/trisacrypto/trisa/pkg/trisa/envelope" +) + +func TestStateType(t *testing.T) { + require.Equal(t, "clear", envelope.Clear.String()) + require.Equal(t, "unsealed", envelope.Unsealed.String()) + require.Equal(t, "sealed", envelope.Sealed.String()) + require.Equal(t, "error", envelope.Error.String()) + require.Equal(t, "clear-error", envelope.ClearError.String()) + require.Equal(t, "unsealed-error", envelope.UnsealedError.String()) + require.Equal(t, "sealed-error", envelope.SealedError.String()) + require.Equal(t, "corrupted", envelope.Corrupted.String()) + + require.Equal(t, "unknown", envelope.State(0).String()) + require.Equal(t, "unknown", envelope.State(42).String()) + + // These only work because of the current positions of the states, e.g. Error==4 + // and Clear-Sealed and ClearError-UnsealedError are 1-3 and 5-7 respectively. + require.Equal(t, envelope.ClearError, envelope.Clear|envelope.Error) + require.Equal(t, envelope.UnsealedError, envelope.Unsealed|envelope.Error) + require.Equal(t, envelope.SealedError, envelope.Sealed|envelope.Error) +} + +func TestEnvelopeState(t *testing.T) { + // No payload, no envelope should return an invalid state + env := &envelope.Envelope{} + require.Equal(t, envelope.Unknown, env.State()) +} diff --git a/pkg/trisa/envelope/testdata/error_envelope.json b/pkg/trisa/envelope/testdata/error_envelope.json new file mode 100644 index 0000000..f4220cb --- /dev/null +++ b/pkg/trisa/envelope/testdata/error_envelope.json @@ -0,0 +1,18 @@ +{ + "id": "2b3b4c95-0a78-4f2a-a9fa-041970f97144", + "payload": "", + "encryption_key": "", + "encryption_algorithm": "", + "hmac": "", + "hmac_secret": "", + "hmac_algorithm": "", + "error": { + "code": "COMPLIANCE_CHECK_FAIL", + "message": "specified account has been frozen temporarily", + "retry": false, + "details": null + }, + "timestamp": "2022-01-27T08:21:43Z", + "sealed": false, + "public_key_signature": "" +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/payload.json b/pkg/trisa/envelope/testdata/payload.json new file mode 100644 index 0000000..c71c8c6 --- /dev/null +++ b/pkg/trisa/envelope/testdata/payload.json @@ -0,0 +1,243 @@ +{ + "identity": { + "@type": "type.googleapis.com/ivms101.IdentityPayload", + "originator": { + "originator_persons": [ + { + "natural_person": { + "name": { + "name_identifiers": [ + { + "primary_identifier": "Howard", + "secondary_identifier": "Jane", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "primary_identifier": "Price", + "secondary_identifier": "Jane", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_MAID" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_HOME", + "department": "", + "sub_department": "", + "street_name": "Greystone Street", + "building_number": "28", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "38017", + "town_name": "Collierville", + "town_location_name": "", + "district_name": "", + "country_sub_division": "TN", + "address_line": [], + "country": "US" + } + ], + "national_identification": { + "national_identifier": "112502920", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_SOCS", + "country_of_issue": "US", + "registration_authority": "RA000748" + }, + "customer_identification": "2642", + "date_and_place_of_birth": { + "date_of_birth": "1992-10-04", + "place_of_birth": "West Islip, NY" + }, + "country_of_residence": "US" + } + } + ], + "account_numbers": [ + "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v" + ] + }, + "beneficiary": { + "beneficiary_persons": [ + { + "natural_person": { + "name": { + "name_identifiers": [ + { + "primary_identifier": "Clark", + "secondary_identifier": "Lawrence", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "primary_identifier": "Clark", + "secondary_identifier": "Larry", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_ALIA" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_HOME", + "department": "", + "sub_department": "", + "street_name": "Watling St", + "building_number": "249", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "WD7 7AL", + "town_name": "Radlett", + "town_location_name": "", + "district_name": "", + "country_sub_division": "", + "address_line": [], + "country": "United Kingdom" + } + ], + "national_identification": { + "national_identifier": "319560446", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_DRLC", + "country_of_issue": "GB", + "registration_authority": "RA000591" + }, + "customer_identification": "5610", + "date_and_place_of_birth": { + "date_of_birth": "1986-12-13", + "place_of_birth": "Leeds, United Kingdom" + }, + "country_of_residence": "GB" + } + } + ], + "account_numbers": [ + "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3" + ] + }, + "originating_vasp": { + "originating_vasp": { + "legal_person": { + "name": { + "name_identifiers": [ + { + "legal_person_name": "AliceCoin, Inc.", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "legal_person_name": "Alice VASP", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_SHRT" + }, + { + "legal_person_name": "AliceCoin", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_TRAD" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_BIZZ", + "department": "", + "sub_department": "", + "street_name": "Roosevelt Place", + "building_number": "23", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "02151", + "town_name": "Boston", + "town_location_name": "", + "district_name": "", + "country_sub_division": "MA", + "address_line": [], + "country": "US" + } + ], + "customer_number": "", + "national_identification": { + "national_identifier": "5493004YBI24IF4TIP92", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_LEIX", + "country_of_issue": "US", + "registration_authority": "RA000744" + }, + "country_of_registration": "US" + } + } + }, + "beneficiary_vasp": { + "beneficiary_vasp": { + "legal_person": { + "name": { + "name_identifiers": [ + { + "legal_person_name": "Bob's Discount VASP, PLC", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "legal_person_name": "Bob VASP", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_SHRT" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_BIZZ", + "department": "", + "sub_department": "", + "street_name": "Grimsby Road", + "building_number": "762", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "OX8 U89", + "town_name": "Oxford", + "town_location_name": "", + "district_name": "", + "country_sub_division": "", + "address_line": [], + "country": "GB" + } + ], + "customer_number": "", + "national_identification": { + "national_identifier": "213800AQUAUP6I215N33", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_LEIX", + "country_of_issue": "GB", + "registration_authority": "RA000589" + }, + "country_of_registration": "GB" + } + } + }, + "transfer_path": { + "transfer_path": [] + }, + "payload_metadata": { + "transliteration_method": [] + } + }, + "transaction": { + "@type": "type.googleapis.com/trisa.data.generic.v1beta1.Transaction", + "txid": "05d9dc3fcbf48771c8ee9e95200877ef08e2766a780d4e44eee397633eb164d0", + "originator": "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v", + "beneficiary": "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3", + "amount": 0.00206412, + "network": "btc", + "timestamp": "2022-01-30T16:14:00Z", + "extra_json": "{\"value_when_transacted\": \"USD $77.86\"}", + "asset_type": "BTC", + "tag": "" + }, + "sent_at": "2022-01-27T08:21:43Z", + "received_at": "2022-01-30T16:28:39Z" +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/payload/identity.json b/pkg/trisa/envelope/testdata/payload/identity.json new file mode 100644 index 0000000..e7586d2 --- /dev/null +++ b/pkg/trisa/envelope/testdata/payload/identity.json @@ -0,0 +1,162 @@ +{ + "originator": { + "originator_persons": [{ + "natural_person": { + "name": { + "name_identifiers": [{ + "primary_identifier": "Howard", + "secondary_identifier": "Jane", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "primary_identifier": "Price", + "secondary_identifier": "Jane", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_MAID" + } + ] + }, + "geographic_addresses": [{ + "address_type": "ADDRESS_TYPE_CODE_HOME", + "street_name": "Greystone Street", + "building_number": "28", + "post_code": "38017", + "town_name": "Collierville", + "country_sub_division": "TN", + "country": "US" + }], + "national_identification": { + "national_identifier": "112502920", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_SOCS", + "country_of_issue": "US", + "registration_authority": "RA000748" + }, + "customer_identification": "2642", + "date_and_place_of_birth": { + "date_of_birth": "1992-10-04", + "place_of_birth": "West Islip, NY" + }, + "country_of_residence": "US" + } + }], + "account_numbers": [ + "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v" + ] + }, + "beneficiary": { + "beneficiary_persons": [{ + "natural_person": { + "name": { + "name_identifiers": [{ + "primary_identifier": "Clark", + "secondary_identifier": "Lawrence", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "primary_identifier": "Clark", + "secondary_identifier": "Larry", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_ALIA" + } + ] + }, + "geographic_addresses": [{ + "address_type": "ADDRESS_TYPE_CODE_HOME", + "street_name": "Watling St", + "building_number": "249", + "post_code": "WD7 7AL", + "town_name": "Radlett", + "country": "United Kingdom" + }], + "national_identification": { + "national_identifier": "319560446", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_DRLC", + "country_of_issue": "GB", + "registration_authority": "RA000591" + }, + "customer_identification": "5610", + "date_and_place_of_birth": { + "date_of_birth": "1986-12-13", + "place_of_birth": "Leeds, United Kingdom" + }, + "country_of_residence": "GB" + } + }], + "account_numbers": [ + "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3" + ] + }, + "originating_vasp": { + "originating_vasp": { + "legal_person": { + "name": { + "name_identifiers": [{ + "legal_person_name": "AliceCoin, Inc.", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "legal_person_name": "Alice VASP", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_SHRT" + }, + { + "legal_person_name": "AliceCoin", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_TRAD" + } + ] + }, + "geographic_addresses": [{ + "address_type": "ADDRESS_TYPE_CODE_BIZZ", + "street_name": "Roosevelt Place", + "building_number": "23", + "post_code": "02151", + "town_name": "Boston", + "country_sub_division": "MA", + "country": "US" + }], + "national_identification": { + "national_identifier": "5493004YBI24IF4TIP92", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_LEIX", + "country_of_issue": "US", + "registration_authority": "RA000744" + }, + "country_of_registration": "US" + } + } + }, + "beneficiary_vasp": { + "beneficiary_vasp": { + "legal_person": { + "name": { + "name_identifiers": [{ + "legal_person_name": "Bob's Discount VASP, PLC", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "legal_person_name": "Bob VASP", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_SHRT" + } + ] + }, + "geographic_addresses": [{ + "address_type": "ADDRESS_TYPE_CODE_BIZZ", + "street_name": "Grimsby Road", + "building_number": "762", + "post_code": "OX8 U89", + "town_name": "Oxford", + "country": "GB" + }], + "national_identification": { + "national_identifier": "213800AQUAUP6I215N33", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_LEIX", + "country_of_issue": "GB", + "registration_authority": "RA000589" + }, + "country_of_registration": "GB" + } + } + }, + "transfer_path": { + "transfer_path": [] + }, + "payload_metadata": { + "transliteration_method": [] + } +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/payload/pending.json b/pkg/trisa/envelope/testdata/payload/pending.json new file mode 100644 index 0000000..b26b54e --- /dev/null +++ b/pkg/trisa/envelope/testdata/payload/pending.json @@ -0,0 +1,20 @@ +{ + "envelope_id": "2b3b4c95-0a78-4f2a-a9fa-041970f97144", + "received_by": "BobVASP Compliance Review", + "received_at": "2022-01-27T08:21:43Z", + "message": "We have flagged this message as needing further review and will respond shortly.", + "reply_not_after": "2022-01-30T08:22:00Z", + "reply_not_before": "2022-01-27T20:22:00Z", + "extra_json": "", + "transaction": { + "txid": "", + "originator": "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v", + "beneficiary": "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3", + "amount": "0.00206412", + "network": "btc", + "timestamp": "", + "extra_json": "", + "asset_type": "BTC", + "tag": "" + } +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/payload/transaction.json b/pkg/trisa/envelope/testdata/payload/transaction.json new file mode 100644 index 0000000..c4d9d0a --- /dev/null +++ b/pkg/trisa/envelope/testdata/payload/transaction.json @@ -0,0 +1,11 @@ +{ + "txid": "05d9dc3fcbf48771c8ee9e95200877ef08e2766a780d4e44eee397633eb164d0", + "originator": "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v", + "beneficiary": "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3", + "amount": "0.00206412", + "network": "btc", + "timestamp": "2022-01-30T16:14:00Z", + "extra_json": "{\"value_when_transacted\": \"USD $77.86\"}", + "asset_type": "BTC", + "tag": "" +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/pending_payload.json b/pkg/trisa/envelope/testdata/pending_payload.json new file mode 100644 index 0000000..8e58753 --- /dev/null +++ b/pkg/trisa/envelope/testdata/pending_payload.json @@ -0,0 +1,252 @@ +{ + "identity": { + "@type": "type.googleapis.com/ivms101.IdentityPayload", + "originator": { + "originator_persons": [ + { + "natural_person": { + "name": { + "name_identifiers": [ + { + "primary_identifier": "Howard", + "secondary_identifier": "Jane", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "primary_identifier": "Price", + "secondary_identifier": "Jane", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_MAID" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_HOME", + "department": "", + "sub_department": "", + "street_name": "Greystone Street", + "building_number": "28", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "38017", + "town_name": "Collierville", + "town_location_name": "", + "district_name": "", + "country_sub_division": "TN", + "address_line": [], + "country": "US" + } + ], + "national_identification": { + "national_identifier": "112502920", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_SOCS", + "country_of_issue": "US", + "registration_authority": "RA000748" + }, + "customer_identification": "2642", + "date_and_place_of_birth": { + "date_of_birth": "1992-10-04", + "place_of_birth": "West Islip, NY" + }, + "country_of_residence": "US" + } + } + ], + "account_numbers": [ + "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v" + ] + }, + "beneficiary": { + "beneficiary_persons": [ + { + "natural_person": { + "name": { + "name_identifiers": [ + { + "primary_identifier": "Clark", + "secondary_identifier": "Lawrence", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "primary_identifier": "Clark", + "secondary_identifier": "Larry", + "name_identifier_type": "NATURAL_PERSON_NAME_TYPE_CODE_ALIA" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_HOME", + "department": "", + "sub_department": "", + "street_name": "Watling St", + "building_number": "249", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "WD7 7AL", + "town_name": "Radlett", + "town_location_name": "", + "district_name": "", + "country_sub_division": "", + "address_line": [], + "country": "United Kingdom" + } + ], + "national_identification": { + "national_identifier": "319560446", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_DRLC", + "country_of_issue": "GB", + "registration_authority": "RA000591" + }, + "customer_identification": "5610", + "date_and_place_of_birth": { + "date_of_birth": "1986-12-13", + "place_of_birth": "Leeds, United Kingdom" + }, + "country_of_residence": "GB" + } + } + ], + "account_numbers": [ + "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3" + ] + }, + "originating_vasp": { + "originating_vasp": { + "legal_person": { + "name": { + "name_identifiers": [ + { + "legal_person_name": "AliceCoin, Inc.", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "legal_person_name": "Alice VASP", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_SHRT" + }, + { + "legal_person_name": "AliceCoin", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_TRAD" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_BIZZ", + "department": "", + "sub_department": "", + "street_name": "Roosevelt Place", + "building_number": "23", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "02151", + "town_name": "Boston", + "town_location_name": "", + "district_name": "", + "country_sub_division": "MA", + "address_line": [], + "country": "US" + } + ], + "customer_number": "", + "national_identification": { + "national_identifier": "5493004YBI24IF4TIP92", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_LEIX", + "country_of_issue": "US", + "registration_authority": "RA000744" + }, + "country_of_registration": "US" + } + } + }, + "beneficiary_vasp": { + "beneficiary_vasp": { + "legal_person": { + "name": { + "name_identifiers": [ + { + "legal_person_name": "Bob's Discount VASP, PLC", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_LEGL" + }, + { + "legal_person_name": "Bob VASP", + "legal_person_name_identifier_type": "LEGAL_PERSON_NAME_TYPE_CODE_SHRT" + } + ], + "local_name_identifiers": [], + "phonetic_name_identifiers": [] + }, + "geographic_addresses": [ + { + "address_type": "ADDRESS_TYPE_CODE_BIZZ", + "department": "", + "sub_department": "", + "street_name": "Grimsby Road", + "building_number": "762", + "building_name": "", + "floor": "", + "post_box": "", + "room": "", + "post_code": "OX8 U89", + "town_name": "Oxford", + "town_location_name": "", + "district_name": "", + "country_sub_division": "", + "address_line": [], + "country": "GB" + } + ], + "customer_number": "", + "national_identification": { + "national_identifier": "213800AQUAUP6I215N33", + "national_identifier_type": "NATIONAL_IDENTIFIER_TYPE_CODE_LEIX", + "country_of_issue": "GB", + "registration_authority": "RA000589" + }, + "country_of_registration": "GB" + } + } + }, + "transfer_path": { + "transfer_path": [] + }, + "payload_metadata": { + "transliteration_method": [] + } + }, + "transaction": { + "@type": "type.googleapis.com/trisa.data.generic.v1beta1.Pending", + "envelope_id": "2b3b4c95-0a78-4f2a-a9fa-041970f97144", + "received_by": "BobVASP Compliance Review", + "received_at": "2022-01-27T08:21:43Z", + "message": "We have flagged this message as needing further review and will respond shortly.", + "reply_not_after": "2022-01-30T08:22:00Z", + "reply_not_before": "2022-01-27T20:22:00Z", + "extra_json": "", + "transaction": { + "txid": "", + "originator": "14HmBSwec8XrcWge9Zi1ZngNia64u3Wd2v", + "beneficiary": "14WU745djqecaJ1gmtWQGeMCFim1W5MNp3", + "amount": 0.00206412, + "network": "btc", + "timestamp": "", + "extra_json": "", + "asset_type": "BTC", + "tag": "" + } + }, + "sent_at": "2022-01-27T08:21:43Z", + "received_at": "" +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/sealed_envelope.json b/pkg/trisa/envelope/testdata/sealed_envelope.json new file mode 100644 index 0000000..4c7d880 --- /dev/null +++ b/pkg/trisa/envelope/testdata/sealed_envelope.json @@ -0,0 +1,13 @@ +{ + "id": "1e7bb3cc-53ff-41b8-870f-4c22110e6f04", + "payload": "OrBvMmcdF2opQxsRGYMh6b9z+WpTKjhhRfis8ZjTkSmHnuoRlF3saF3NTAPIrKPoSA4jaAiEcL/cWa165UfHHu08W+iXgb5eB7scNSgqFPDYVlS1fVbTAUNwedgcoZ3EenF5+t1ky0SQ7ejOzp8FK6l49Meh+UfQf62ELFQi5jBMcaYokvmDJDwgOkJ7rVmZ0NA+LSxyZCoMKbQuiOHapYg1yCgAfVfpazO7d5b54BihZrW7PArpDqQeocpcwCWN4RXJYuHkjRwZmUM3Ks9gKP8tbZgf30giCejxGXgKw02sDplC+9kG8Mfe8XhkaART6NqhDZu+l1ajtI+oFuNM3tJ4XZ+MIDcvA2bs4sCu+g8AILVqNzbHPw36BbmytaFENyYILllMTPSZVxw7FxWCUXfxX0sbCGELHCju2O8Kjdam2v+KHnWM1QeUCOQNLJsXsiWgzYXArlHA1fts/axUcbyyXPYNAyEkH+zRsOsoLmkTVgTdCpFO1QIQ45zZrXZlHZ51q9fa7BBOTnDtFQOkJ6Rzqmuv/gHLq5hFwDdy1/gUOQAYQxvk0daNuBN4n+yLmn7d2Z3iQlEmWteGmU1+vJSY0L/xT0HXeLjdAAbVkA2FxqnBt8ScN48wEhulIeZQLuaIC4h/33Pi643Xy06Q1HEY4IVv9BDR/EeyeszDbr+3+5b0S8XCZmgMZyZL2Qkiny1PVYUbZx/LJAXXYTcE+zGiltGVuiN0AueF/8ymBl95Yr61UZg1YjjdjqaI56yM3Aq13HQZtLL5ccFS8mTN5YsomckQJzb5+J8rW/zcWbUsgYSgHJnv/olPvS18Y3+GjYRqQ0r3/7c3e75otf5GbqcCIwuyrVDEMK14RBG7p6vZYcfP5dNTvVOtoUrqhBze/YlZOVfk0nrQHOytluLKnHnYG5c/MTscSWkE2/r/Bi6MGwzHtrMjY2Q96NqN+G7bQyRiPHSj+aCnMuwNw2/+/rhGAQCz9gW6UbGGvH0uMbyqNtx+oTSnf5as7EbkkRK41LDyUG7AErcx9B+z/4wfFi53mqb52W6orrtEiNIAFhvuXK5mS5fBiWUi4nxrUUnNiAUWvu6CvzHVa8UOXOPsrEawoK4PtjuJO75cKNveFTBq6gh2FFyTLryhu7RkfvIwto1ec8PoFl6togcMiJqBwTuzraXAqh6GRQAp5ELJEWskc1U89UR2GCWnJB8nOp0OCw/wjx/UddFxouZ8aCHY8OzJAA9RgL7lDSSf/HplH+RdRUDMGonKl/FBjJggeGmdWaGFlJW83m+wPuCkxekS7avj1V9btrv7Qpj7qY6F3Pv+rqwbvmZ4hlY/6GI0YeT7ViB9GYbhgMRfkX03jnpH9wnsn7LlM+Dr65yB84rmhGm/Sl6mfXjbD/NOtAdldeIScgy+UnaQlKK1nZmscTe/X4XAZaFfBopEn1WGwByF9yoN5cXpbAZdSH66BYQBQObAToGIrdMjnPqMx8Ju7pDx0+AS/Q/4cgBr0bYeqTgPPcTk1Nko9zK0li+w43WFXi4jpORwj0BQD/qTM2cVuyKJBkJDlHi4mtwN8cpR/kdcRuj8HwZVYMaemDU2BhfH3w==", + "encryption_key": "pXpr/U87nsKHaUNQASTtN07Et1K+k0yZ/faQLdhmaUUiUuOh2+ikRNu2FFNBvoV3DcgJ+LVaeXjUz53lgdxr4QSzJZ9iIWJfjSsgSbYRJnw5UbdtNMBwL5CAFHb97Zg0Y1YYETEC1PbMWgYFX9nGhZqt4YBvQ2Jh08T+ct7Ui83RB7YBpOMQs3jYMLbuDFblmzBDBNLK6RVQCBYbg0HdGhl6NBqwC+FEGvHpwIta26ggQ1Mf54KnfK+yTeS2Kc0RaxvkOdpUpSfRzFsOyGiCAh+Smu4m7d+TuU10pNA9GR9yotDv2UDdJhyTNuLzYYsQAWENUmOXdeRtuNk7gAdQ3wDajAjhmia9jKjZM3Yja1ToenIsWqCdwTBoa+QxSlOlR9MB2wUx/BfVGT/IIzCSboTdqu5YRSjdNr4jo6o1oAD0v8v73qUERjzRgoWHepqq0H177tkIe+PrewmJTaZP4fQs4sMwBua6Zjlr+T4ka0NZ1sqptRTr6IO+zbuJPGlgrk7B1wMAqHgitVoMZHr4o+yZX2Zaq+npwIrjjSulGBlPyGGN0mzIuoKE3oxhOMTpBtG9ORzAXvYEw3ADkyqkfB32c+c79TzvcRgFfM2F+0Ig3mxeTAyqRv4EaOzrSZjfCsaQiemnOEBzoOM+iE8RYb/CZEC99JxcQ9V+zAWr9VU=", + "encryption_algorithm": "AES256-GCM", + "hmac": "vzKqwO072JrDvVaoOZZ9WXjA+BM1n607PFGLEdZ0DAg=", + "hmac_secret": "nmQD4a2fAuCzMviIEo59D4tsU2aZbSFZtnT9qrlo/XPCE9vi0GYPfcRwgCEeVtMtjjiOaYHmsbSrw7Z8pZ2l5/V2hARCz1IXA1iUYvDPbRTAWrAOeQ6wd/igzimtB7bBnfe7fHV8BjrsgEpAjKkbkX97KPm+O10tDlf6I8fjpBLaGMk5QmLJPOBianuNRgzMVcadMK4pHBit0I01brGYL5nKAVowF5QbBLRbyHLU/QIw0/aFpjn1GX+jeeSRtDXWQPAEHDn9FnC4zrWB8ctzKHRLp/BPDwXrP3TazUOcEe96jWR+7u++nh8U+T8/+SDeNyH2JglS99nXbVZ8KvqkBBt9wlYpK7KeKQgvx6RzMfVl4j/yP0GAf70AslWUfpd4/s1iBMsho62yrbZ3HS1vqUd+qizNEbK1WX6czDfpyia3vR4gg0UWhfWmLcqZIokvu7FwrK+yWlLQDBVoPvNGpIg6JWCCFrQOpnMRtmDYXRIrAXy7z90ZhuS5Ul7PbLAqWE2nKIzOXl8PdE4UoNolkuO4CC0I1fQyMD+m5sJPpDdcmGGKbd67IGgiuoQAoSF96pl8y4JRfKoS9yQQ+XCaSk4fwS4kMsEEmleIQPJBzH4HKaETx/Rq/4Voy0tY/Io6VU6tbrF+kS2E0bP2uKHbHrR3eSZPX7MnNkSIRf2gkGI=", + "hmac_algorithm": "HMAC-SHA256", + "error": null, + "timestamp": "2022-03-29T09:16:29.755212-05:00", + "sealed": true, + "public_key_signature": "SHA256:QhEspinUU51gK0dQGqLa56BA6xyRy5/7sN5/6GlaLZw" +} \ No newline at end of file diff --git a/pkg/trisa/envelope/testdata/sealing_key.pem b/pkg/trisa/envelope/testdata/sealing_key.pem new file mode 100644 index 0000000..cab96fe --- /dev/null +++ b/pkg/trisa/envelope/testdata/sealing_key.pem @@ -0,0 +1,52 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDp4UX9z6zjt6Sa +R8pgB4oCEnDneBlh55WNJWLC5JYYPp9nQYzcH5Hl/gxAOEUZYFdswnzA/54ZuuBC +3k6gRQdNT7RVMlK6BNfldN4G3k5Y5LS6DU3zoTHdWlXlvWeogXXbdOnYfud00iKy +nXveZ7Xns7SVAKETrcqX0mOm2mp1S1rwnJBc8mnDxz9p2KieUcJCwLNAoS71F2AQ +o1hiJyNfIN80TEJ0EQCL825XlGMFa3/UTifFj6Ed0c+VE0z3Fr1UjU4DMtvAbFAE +h7Pe+pza773j5wIwWs0czfoJsLIU8UffcGSwBYHel1AjkKRtamKhQyBPXO6gg2sM +E7en03OP8VXVuF+KSUSI5Aj7TGmJUz2TvTZbXevDSQ8+razMzhvPoo4AHAvw+6Dc +kZusNWIQmsvoG4mNtIb2u7yqjaXOR17u4aGFcZIdC9jP/9Kex3VoPPbBKGeLXJj7 +Zt37lTK9NMrh+EPtzyDjScpo1TC7JeSJLZDlaHNrZ33Jrat3DKczUKtizS2jQGn3 +DVwbLPZHInr5gdpjHhXYZUf1vvuFecg3JWXGtj/Jn4rQKPvIITkWF1TsJNhiM/Mp +3YEJlOI8nEquRHgFo+g5nYw/xaJt8F0doEDvDiM0O3aEangodJkAf/U0wBwQGJOG +fuI2Jldv7jiqKSZOKjyz/d6q/2OVHQIDAQABAoICAHW80l9TjNWWPWbtfROat/8T +CYN9EkoXK1JC39T8/hJ2dAinFyI5Qlll6wTpTt/XgCtgPS1rgUuyp/MBttM37NUe +qsm466x5g7YffyY2A3r8p36B9BwRqeik8VkWYHiTs6Em2hIC0MIehxkj7pen3gsm +jGH2TyB4/OQxJbl5et5RcRZvur0Py+6i/2lpiHkq74BrQdHnUpxPiCzYDkZbLZNW +GQda3JlRddT+Vy3/pVm1JETck2kBRTqdxMJnVPeOmwFVoVA66iWUYTPmnMZDo3MT +/aQdRxzQmTFG//ig2wVv8Z6qFgYGv7ouskk1J5FtXPmRsVJCll53ygvqYSo7aJC3 +mrjN9AZYyH2a5V/eV2qclUBGVtyRuZnHoKRiXgt8jG2KifLMI0HWZjy5Qrr2CcPv +EBapVgNIbpIvWdWE4TytzUIjU+GF2R63t55j0eUYUYR9XJ7SYrbQBTQn9DsSMWje +P98DrwUUWhSuAG8pg7mTwlh9e+ZOf2Z5zQxQl7NzdhyDRDiAeK84xci/YoPlQK+K +ZH1mZPL2ZokVc/Gba0RMDBhrxyhCTt83LML+oGzKwDihESgXU8RYrBwTr12nDR6Z +TUz8V+jHTYIJuqyzyDvLulKNzLLAmPvm8cf738h9XCAYUK5ppL/ftNwCYmjpJpnY +qUPYUoIGFso1puvI4+gBAoIBAQDuWLlAzilHozw05TmERX5M4B7JohgT+l7LRT6R +JHTnhHjvOHkZWwGvuA9ZRkbpDMJ7ZtI7LrjCCFXpEdXZMp8cUshOilAX37qjwN94 +TXVvqCEw+TYtDVLIVGTIAsrsOWgGYv+3Wl/jY5h2ghqXuTM0WqvL87u5Sf5Ff8Ry +eLFgUuqr3m/6o56HPQFASDBPrA0ha9jxFr0Ysx5LkOeMEHqoIB3aTZhsceW+tz9U +uoFJirEZVsom6pXbpfw9Jwz151M9NCHrcsuUHmaFpv/HbOV/xtFoKE32FBcuDkPc +ta4uEtTT+reYkbrhIO0be+AmjRinlEs86fZUBq7fMbEeyw2RAoIBAQD7M9vXUCqH +WZ1bMLWWDSE3h7FSv3HnMdypCXkC8EZQLrmYVcveBQA69k9VozSv3auKQgfGL0fs +ZZdnTqw91UBuwcjV9iWB9qHXaHdkoaWp0Ie9BYSoaE2KHlE98/8yr2B7T9HWh6e3 +2cbp9mNExPo6kjZGZAYYpWo0tjapad0s4DPWdJXru46k5m4wECrdwEHdJekcCzYS +mJzU9VMbmhkKJHa32rHQs47pbJC9ogaxlwcfr5kcgJQhy6bMtXnXA09fvZtrW1kb +CZDdUlQ93EV90uWbbE2Ppltge4EBqB+qzXQEfgl9Tevm1jOm80i/2zPGPteV5rj7 +A/wVmpctsjjNAoIBADSt7XwSVPNdc9ApHdZLWcf5/TQJnJLF0q/QxSxlR2VoVSZi +b/mXsL66ysGrk76ssEuABVVJVVKlbv+Njptr4djrvxQE5ADy1RieZ4X6cMtV+MgP +PNcmS7a1WHUQkPM3GPPYa7aFxg3HFIqU5PXF3DhlFfWFEGe6n/WScMPwQxwMF4wX +JN7bzQ0NAbrIec0SNPC/Gnrm0LKl3DtNcq/1cHa6an62icrmPaYycE/0zOCJQ+1a +zmFdlfOvMxn9CJNNJouvexPEEpTRR02hBIV6XxsnwV7pEZojBKCMTJl6ml3akams +j2msRAbANUfO0FMU+m8Hir2S5Hvb4ki4ffJVZNECggEBAJAJOkMJWnllbw68rUIu +oR9AFPMHhv4z3vyly9ddglOzlwO8HjtdzrYASZknDazRg+yw09hVf/4Sem/n8mc6 +AcoAH3Jfwz3z9vSWl+8BqQXn5g0CPwB5XgyWOas2XtvnJ4FNUVqbShXnL2Ezv9PM +xlYTRD+0VrKHFpDG8izN4N48aDzNeLFESBRRdFjmzE1+UcXGWRWVcBnVnAeX62Fm +SHlCUzg0k8TBUG6Tq6KwZvopSRJoE3j+WPHP3gJ/BC+/XCHfjGBQbpVMNN5SuK8H +wGjXJoSp4wc9MiPubRhQGGhNifqRckuBBITFybQux0YLIHLJ0b/IHcUAkeipTYoM +KPkCggEAazP7LQhPxN6ADtNT+CWzRuOED+VvDkOMWo7w2SPtOUnPe9qQVo+Ow+5o +0p5OS3vN46xoIalkd56O76Ffxm9ktXhdOkDsMreJzNH/mZxgwY1tspG1adHIafAS +CpBMl1MM550hT1UkKJGDf5O0o5QYNa6FmSIzhnkoh7wZQWa7jUhuA2zPAYtYslBq +EbLBYHhNmYilCLjFRJ/5p6EZEgXy9SIDMg4hAnfAgV5WBsE0EvfMrfvECVWlRGzg +4fyxcOt6Bw+V/N7n4OeITUu/T9LOJK6frLU1ubqqB7hfrp7URbUTuoOrVhqS+7Yv +TEhLPeGjrU0RkgsoMo8U48UEgITbgA== +-----END RSA PRIVATE KEY----- diff --git a/pkg/trisa/envelope/testdata/unsealed_envelope.json b/pkg/trisa/envelope/testdata/unsealed_envelope.json new file mode 100644 index 0000000..da566a9 --- /dev/null +++ b/pkg/trisa/envelope/testdata/unsealed_envelope.json @@ -0,0 +1,13 @@ +{ + "id": "ca4344e6-fb1e-4ba2-9ac7-f78fab5f00c0", + "payload": "4WNgs+J/zFmcrb3ebzQTaaAomwbk6NJE6ZnQrA==", + "encryption_key": "a7syjk4CU70HkQq91ecqLsMJ4guEnByycgEfXJ9HaB8=", + "encryption_algorithm": "AES256-GCM", + "hmac": "Wmbakab7EwzGNFSiiJcvoIuQig6WDbn5d+ynsk8fg6o=", + "hmac_secret": "a7syjk4CU70HkQq91ecqLsMJ4guEnByycgEfXJ9HaB8=", + "hmac_algorithm": "HMAC-SHA256", + "error": null, + "timestamp": "2022-03-29T09:16:27.453444-05:00", + "sealed": false, + "public_key_signature": "" +} \ No newline at end of file diff --git a/pkg/trisa/handler/handler.go b/pkg/trisa/handler/handler.go index aa1eaef..d17c390 100644 --- a/pkg/trisa/handler/handler.go +++ b/pkg/trisa/handler/handler.go @@ -102,7 +102,6 @@ func Open(in *protocol.SecureEnvelope, key interface{}) (_ *Envelope, err error) if err = proto.Unmarshal(payloadData, env.Payload); err != nil { return nil, protocol.Errorf(protocol.EnvelopeDecodeFail, "could not unmarshal payload from decrypted data: %s", err) } - return env, nil } diff --git a/proto/trisa/api/v1beta1/errors.proto b/proto/trisa/api/v1beta1/errors.proto index 4266609..101892b 100644 --- a/proto/trisa/api/v1beta1/errors.proto +++ b/proto/trisa/api/v1beta1/errors.proto @@ -36,7 +36,8 @@ message Error { UNKNOWN_WALLET_ADDRESS = 51; // VASP does not have KYC information for the specified wallet address. - UNKOWN_IDENTITY = 52; + UNKNOWN_IDENTITY = 52; + UNKOWN_IDENTITY = 52; // Typo left for backwards compatibility. // Specifically, the Originator account cannot be identified. UNKNOWN_ORIGINATOR = 53;