├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README-CloudHSM.md
├── README-KMS.md
├── README.md
├── images
├── cloudhsm-id.jpg
├── hsm.jpg
├── kms.jpg
├── proxy-cloudhsm-arch.png
├── proxy-cloudhsm.png
├── proxy-kms-arch.png
└── proxy-kms-arch1.png
└── proxy
├── cloudhsm
├── cavium
│ └── pom.xml
├── pom.xml
└── proxy
│ ├── pom.xml
│ └── src
│ └── main
│ ├── docker
│ ├── Dockerfile
│ └── wrapper_script.sh
│ ├── java
│ └── com
│ │ └── amazon
│ │ └── aws
│ │ └── pix
│ │ └── cloudhsm
│ │ └── proxy
│ │ ├── PixCloudHSMProxyRouteBuilder.java
│ │ ├── camel
│ │ └── netty
│ │ │ ├── NettyHttpClientInitializerFactory.java
│ │ │ └── NettySSLContextParameters.java
│ │ └── processor
│ │ ├── CaptureRequestProcessor.java
│ │ ├── LogRequestResponseProcessor.java
│ │ ├── SignRequestProcessor.java
│ │ └── VerifyResponseProcessor.java
│ └── resources
│ └── application.properties
├── core
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── amazon
│ │ └── aws
│ │ └── pix
│ │ └── core
│ │ ├── audit
│ │ └── AuditLog.java
│ │ ├── util
│ │ ├── KeyStoreUtil.java
│ │ └── PixConstants.java
│ │ └── xml
│ │ ├── Iso20022URIDereferencer.java
│ │ ├── Iso20022XmlSigner.java
│ │ ├── X509IssuerSerialKeySelector.java
│ │ └── XmlSigner.java
│ └── test
│ ├── java
│ └── com
│ │ └── amazon
│ │ └── aws
│ │ └── pix
│ │ └── core
│ │ └── test
│ │ └── xml
│ │ ├── Iso20022XmlSignerTest.java
│ │ └── XmlSignerTest.java
│ └── resources
│ ├── security
│ └── client.jks
│ └── xml
│ ├── pacs.008_CONTA_1_msg.xml
│ └── test.xml
├── kms
├── pom.xml
└── src
│ ├── assembly
│ └── zip.xml
│ └── main
│ ├── java
│ └── com
│ │ └── amazon
│ │ └── aws
│ │ └── pix
│ │ └── kms
│ │ └── proxy
│ │ ├── config
│ │ └── Config.java
│ │ ├── service
│ │ ├── Logger.java
│ │ ├── Sender.java
│ │ └── Signer.java
│ │ └── sync
│ │ └── ProxyHandler.java
│ └── resources
│ ├── application.properties
│ ├── reflection-config.json
│ └── resources-config.json
├── pom.xml
└── test
├── pom.xml
└── src
└── main
├── docker
├── Dockerfile
└── ssl
│ ├── dict-response.xml
│ ├── mtls.cer
│ ├── mtls.jks
│ ├── mtls.key
│ ├── mtls.p12
│ ├── sig.cer
│ ├── sig.jks
│ ├── sig.key
│ └── sig.p12
├── java
└── com
│ └── amazon
│ └── aws
│ └── pix
│ └── proxy
│ └── test
│ └── PixProxyTestRouteBuilder.java
└── resources
└── application.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | *.iml
3 | **/target/
4 | /lsp/
5 | *.DS_Store
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *master* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/README-CloudHSM.md:
--------------------------------------------------------------------------------
1 | # AWS CloudHSM architecture to exemplify digital signature and secure message transmission to the Brazilian Instant Payment System
2 |
3 |
4 |
5 |
6 |
7 | This project contains source code and supporting files that includes the following folders:
8 |
9 | - `proxy/cloudhsm` - Proxy that uses AWS CloudHSM.
10 | - `proxy/core` - Sign XML messages.
11 | - `proxy/test` - BACEN simulator.
12 |
13 | The main code of application uses several AWS resources, including AWS CLoudHSM and an AWS Fargate. The audit part of solution use other AWS resources, including [Amazon Kinesis Firehose](https://aws.amazon.com/kinesis/data-firehose/?nc1=h_ls), [Amazon Athena](https://aws.amazon.com/athena/?nc1=h_ls&whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc), [Amazon S3](https://aws.amazon.com/s3/?nc1=h_ls) and [AWS Glue](https://docs.aws.amazon.com/glue/latest/dg/components-overview.html).
14 |
15 |
16 | ## Following is the proposed architecture
17 |
18 | The architecture presented here can be part of a more complete, [event-based solution](https://aws.amazon.com/en/event-driven-architecture/), which can cover the entire payment message transmission flow, from the banking core. For example, the complete solution of the Financial Institution (paying or receiving), could contain other complementary architectures such as **Authorization**, **Undo** (based on the [SAGA model](https://docs.aws.amazon.com/whitepapers/latest/microservices-on-aws/distributed-data-management.html)), **Effectiveness**, **Communication with on-premises** environment ([hybrid environment](https://aws.amazon.com/en/hybrid/)), etc., using other services such as [Amazon EventBridge](https://aws.amazon.com/en/eventbridge/), Amazon Simple Notification Service ([SNS](https://aws.amazon.com/en/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc)), Amazon Simple Queue Service ([SQS](https://aws.amazon.com/en/sqs/)), [AWS Step Functions](https://aws.amazon.com/en/step-functions/), [Amazon ElastiCache](https://aws.amazon.com/en/elasticache/), [Amazon DynamoDB](https://aws.amazon.com/en/dynamodb/).
19 |
20 |
21 |
22 |
23 |
24 |
25 | 1. Store login and password in [AWS Secrets Manager](https://aws.amazon.com/en/secrets-manager/), to communicate with AWS CloudHSM.
26 | 2. Store or import the private key on [AWS CloudHSM](https://aws.amazon.com/cloudhsm/?nc1=h_ls).
27 | 3. Store the three certificates in the [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html): generated key certificate for signature, certificate generated for mTLS, CloudHSM certificate (customer CA).
28 | 4. Service/Application sends transaction request in XML format.
29 | 5. [ELB](https://aws.amazon.com/en/elasticloadbalancing/) balances requests among [AWS Fargate containers](https://aws.amazon.com/en/fargate/).
30 | 6. Application (AWS Fargate) uses AWS CloudHSM for digital signature of XML.
31 | 7. Application (AWS Fargate) uses AWS CloudHSM to establish mTLS and transmit XML to [BACEN](https://www.bcb.gov.br/en/financialstability/instantpayments).
32 | 8. Application (AWS Fargate) receives the response from BACEN and, if necessary, validates the digital signature of the received XML.
33 | 9. Application (AWS Fargate) logs the request log by sending it directly to [Amazon Kinesis Data Firehose](https://aws.amazon.com/en/kinesis/data-firehose/).
34 | 10. The reply message is sent to the ELB.
35 | 11. The reply message is received by the Service/Application.
36 | 12. Amazon Kinesis Data Firehose uses the [AWS Glue Data Catalog](https://aws.amazon.com/en/glue/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) to convert the logs to parquet format.
37 | 13. Amazon Kinesis Data Firehose sends the logs to [Amazon S3](https://aws.amazon.com/en/s3/), already partitioned into “folders” (/year/month/day/hour/).
38 | 14. [Amazon Athena](https://docs.aws.amazon.com/athena/latest/ug/glue-athena.html) uses the AWS Glue Data Catalog as a central place to store and retrieve table metadata.
39 | 15. [AWS Glue crawlers](https://docs.aws.amazon.com/glue/latest/dg/add-crawler.html) automatically update the metadata repository every hour.
40 | 16. You can immediately query the data directly on Amazon S3 using serverless analytics services, such as [Amazon Athena](https://aws.amazon.com/en/athena/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) (ad hoc with standard SQL) and optionally the [Amazon QuickSight](https://aws.amazon.com/en/quicksight/).
41 |
42 |
43 | ## How to deploy?
44 |
45 | ### AWS CloudHSM
46 |
47 | Here are the resources you’ll need in order to follow along with both architectures:
48 |
49 | - An Amazon Virtual Private Cloud (Amazon VPC) with the following components:
50 |
51 | Private subnet in Availability Zone to be used for the HSM’s elastic network interface (ENI).
52 | A public subnet that contains a network address translation (NAT) gateway.
53 | A private subnet with a route table that routes internet traffic (0.0.0.0/0) to the NAT gateway. You’ll use this subnet to run the AWS Fargate application. The NAT gateway allows you to connect to the AWS CloudHSM, [AWS Systems Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-create-vpc.html), and [AWS Secrets Manager endpoints](https://docs.aws.amazon.com/secretsmanager/latest/userguide/vpc-endpoint-overview.html#vpc-endpoint).
54 |
55 | **Note**: For high availability, you can add multiple instances of the public and private subnets. For more information about how to create an Amazon VPC with public and private subnets as well as a NAT gateway, refer to the [Amazon VPC user guide](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Scenarios.html).
56 |
57 | - An **active AWS CloudHSM cluster** with at least one active HSM. The HSMs should be created in the private subnets. You can follow the Getting Started with [AWS CloudHSM guide](https://docs.aws.amazon.com/cloudhsm/latest/userguide/create-cluster.html) to create and initialize the CloudHSM cluster.
58 |
59 | - The **AWS CloudHSM client** installed and configured to connect to the CloudHSM cluster. Optionally, you can use an Amazon Linux 2 EC2 instance with the CloudHSM client installed and configured. The client instance should be launched in the public subnet. You can again refer to [Getting Started With AWS CloudHSM](https://docs.aws.amazon.com/cloudhsm/latest/userguide/getting-started.html) to configure and connect the client instance. Also, install the [AWS CloudHSM Dynamic Engine for OpenSSL](https://docs.aws.amazon.com/cloudhsm/latest/userguide/openssl-library-install.html).
60 |
61 | - The **CO** and **CU credentials** created: CO (crypto officer) and CU (crypto user) by following the steps in the [user guide](https://docs.aws.amazon.com/cloudhsm/latest/userguide/manage-hsm-users.html#create-user).
62 |
63 | #### Generate keys and certificate to digital signature
64 |
65 | You can generate or import a [private key using Open SSL](https://docs.aws.amazon.com/cloudhsm/latest/userguide/ssl-offload-import-or-generate-private-key-and-certificate.html).
66 | We recommend that private key should be non-extractable.
67 |
68 | - Generating a [NON-EXTRACTABLE](https://docs.aws.amazon.com/cloudhsm/latest/userguide/key_mgmt_util-genRSAKeyPair.html) private key:
69 |
70 | Launch the key management util:
71 | ```
72 | $ /opt/cloudhsm/bin/key_mgmt_util
73 | ```
74 |
75 | Login:
76 | ```
77 | Command: loginHSM -u CU -s -p
78 | ```
79 |
80 | Generate the key pair:
81 | ```
82 | Command: genRSAKeyPair -m 2048 -e 65541 -l -nex
83 |
84 | Cfm3GenerateKeyPair: public key handle: private key handle:
85 | ```
86 |
87 | Exit
88 | ```
89 | Command: exit
90 | ```
91 |
92 | Launch the CloudHSM management util:
93 | ```
94 | $ /opt/cloudhsm/bin/cloudhsm_mgmt_util /opt/cloudhsm/etc/cloudhsm_mgmt_util.cfg
95 | ```
96 |
97 | Login:
98 | ```
99 | aws-cloudhsm> loginHSM CU
100 | ```
101 |
102 | Check that private key is not extractable:
103 | ```
104 | aws-cloudhsm> getAttribute 354
105 |
106 | OBJ_ATTR_EXTRACTABLE
107 | 0x00000000
108 | ```
109 |
110 | Note that the public key is always extractable:
111 | ```
112 | aws-cloudhsm> getAttribute 354
113 |
114 | OBJ_ATTR_EXTRACTABLE
115 | 0x00000001
116 | ```
117 |
118 | Check the private key label:
119 | ```
120 | aws-cloudhsm> getAttribute 3
121 |
122 | OBJ_ATTR_LABEL
123 |
124 | ```
125 |
126 | Check the public key label:
127 | ```
128 | aws-cloudhsm> getAttribute 3
129 |
130 | OBJ_ATTR_LABEL
131 |
132 | ```
133 |
134 | Change the public key label:
135 | ```
136 | aws-cloudhsm> setAttribute 3
137 | ```
138 |
139 | Check the public key label:
140 | ```
141 | aws-cloudhsm> getAttribute 3
142 |
143 | OBJ_ATTR_LABEL
144 |
145 | ```
146 |
147 | Exit
148 | ```
149 | aws-cloudhsm> quit
150 | ```
151 |
152 | Launch the key management util:
153 | ```
154 | $ /opt/cloudhsm/bin/key_mgmt_util
155 | ```
156 | Login:
157 | ```
158 | Command: loginHSM -u CU -s -p
159 | ```
160 |
161 | Export the fake private key:
162 | ```
163 | Command: getCaviumPrivKey -k -out .key
164 | ```
165 |
166 | Exit
167 | ```
168 | Command: exit
169 | ```
170 |
171 | Export the HSM_USER and HSM_PASSWORD to use with OpenSSL:
172 | ```
173 | $ export n3fips_password=:
174 | ```
175 |
176 | Generate the CSR:
177 | ```
178 | $ openssl req -engine cloudhsm -new -key .key -out .csr
179 | ```
180 |
181 | Generate a self-signed certificate (ONLY FOR TEST):
182 | ```
183 | $ openssl x509 -engine cloudhsm -req -days -in .csr -signkey .key -out .cer
184 | ```
185 |
186 | #### Generate keys and certificate to mTLS
187 |
188 | - Generating an extractable key for mTLS (Cavium has JCE, but not JSSE):
189 |
190 | Launch the key management util:
191 | ```
192 | $ /opt/cloudhsm/bin/key_mgmt_util
193 | ```
194 |
195 | Login:
196 | ```
197 | Command: loginHSM -u CU -s -p
198 | ```
199 |
200 | Generate a key pair:
201 | ```
202 | Command: genRSAKeyPair -m 2048 -e 65541 -l
203 |
204 | Cfm3GenerateKeyPair: public key handle: private key handle:
205 | ```
206 |
207 | Exit
208 | ```
209 | Command: exit
210 | ```
211 |
212 | Launch the CloudHSM management util:
213 | ```
214 | $ /opt/cloudhsm/bin/cloudhsm_mgmt_util /opt/cloudhsm/etc/cloudhsm_mgmt_util.cfg
215 | ```
216 |
217 | Login:
218 | ```
219 | aws-cloudhsm> loginHSM CU
220 | ```
221 |
222 | Check that private key is extractable:
223 | ```
224 | aws-cloudhsm> getAttribute 354
225 |
226 | OBJ_ATTR_EXTRACTABLE
227 | 0x00000001
228 | ```
229 |
230 | Public key is always extractable:
231 | ```
232 | aws-cloudhsm> getAttribute 354
233 |
234 | OBJ_ATTR_EXTRACTABLE
235 | 0x00000001
236 | ```
237 |
238 | Check the private key label:
239 | ```
240 | aws-cloudhsm> getAttribute 3
241 |
242 | OBJ_ATTR_LABEL
243 |
244 | ```
245 |
246 | Check the public key label:
247 | ```
248 | aws-cloudhsm> getAttribute 3
249 |
250 | OBJ_ATTR_LABEL
251 |
252 | ```
253 |
254 | Change the public key label:
255 | ```
256 | aws-cloudhsm> setAttribute 3
257 | ```
258 |
259 | Check again the public key label:
260 | ```
261 | aws-cloudhsm> getAttribute 3
262 |
263 | OBJ_ATTR_LABEL
264 |
265 | ```
266 |
267 | Exit
268 | ```
269 | aws-cloudhsm> quit
270 | ```
271 |
272 | Launch the key management util:
273 | ```
274 | $ /opt/cloudhsm/bin/key_mgmt_util
275 | ```
276 |
277 | Login:
278 | ```
279 | Command: loginHSM -u CU -s -p
280 | ```
281 |
282 | Export the fake private key:
283 | ```
284 | Command: getCaviumPrivKey -k -out .key
285 | ```
286 |
287 | Exit
288 | ```
289 | Command: exit
290 | ```
291 |
292 | Export the HSM_USER and HSM_PASSWORD to use with OpenSSL:
293 | ```
294 | $ export n3fips_password=:
295 | ```
296 |
297 | Generate the CSR:
298 | ```
299 | $ openssl req -engine cloudhsm -new -key .key -out .csr
300 | ```
301 |
302 | Generate a self-signed certificate (ONLY FOR TEST):
303 | ```
304 | $ openssl x509 -engine cloudhsm -req -days -in .csr -signkey .key -out .cer
305 | ```
306 |
307 | ### AWS Secrets Manager
308 |
309 | [Create](https://docs.aws.amazon.com/secretsmanager/latest/userguide/tutorials_basic.html) a secret with name `/pix/proxy/cloudhsm/CloudHSMSecret` and value:
310 | ```
311 | {
312 | "HSM_USER": "",
313 | "HSM_PASSWORD": ""
314 | }
315 | ```
316 |
317 | ### Register (log audit)
318 |
319 | 1. Before you can upload data to Amazon S3, you must [create a bucket](https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-bucket.html) in one of the AWS Regions to store your data. After you create a bucket, you can upload an unlimited number of data objects to the bucket
320 |
321 | 2. The AWS Glue Data Catalog contains references to data that is used as sources and targets of your extract, transform, and load (ETL) jobs in AWS Glue. Information in the Data Catalog is stored as metadata tables, where each table specifies a [single data store](https://docs.aws.amazon.com/glue/latest/dg/populate-data-catalog.html).
322 |
323 | 3. [Define a database](https://docs.aws.amazon.com/glue/latest/dg/populate-data-catalog.html) in your Data Catalog.
324 |
325 | 4. [Define two tables](https://docs.aws.amazon.com/glue/latest/dg/tables-described.html): SPI and DICT. Both tables need to have the [Columns Structure](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-tables.html#aws-glue-api-catalog-tables-Column) and [Partition Keys](https://docs.aws.amazon.com/glue/latest/dg/tables-described.html#tables-partition), like example below:
326 |
327 | ```
328 | columns: [
329 | {name: 'request_date', type: glue.Schema.STRING},
330 | {name: 'request_method', type: glue.Schema.STRING},
331 | {name: 'request_path', type: glue.Schema.STRING},
332 | {name: 'request_header', type: glue.Schema.STRING},
333 | {name: 'request_body', type: glue.Schema.STRING},
334 | {name: 'response_status_code', type: glue.Schema.INTEGER},
335 | {name: 'response_signature_valid', type: glue.Schema.STRING},
336 | {name: 'response_header', type: glue.Schema.STRING},
337 | {name: 'response_body', type: glue.Schema.STRING}
338 | ],
339 |
340 | partitionKeys: [
341 | {name: 'year', type: glue.Schema.STRING},
342 | {name: 'month', type: glue.Schema.STRING},
343 | {name: 'day', type: glue.Schema.STRING},
344 | {name: 'hour', type: glue.Schema.STRING}
345 | ]
346 | ```
347 |
348 | The DICT table must be pointed to the S3 bucket that you created and must have the prefix `log/dict`.
349 |
350 | The SPI table must be pointed to the S3 bucket that you created and must have the prefix `log/spi`.
351 |
352 | 5. [Create a crawler](https://docs.aws.amazon.com/glue/latest/dg/add-crawler.html) with target to the created tables (SPI and DICT).
353 |
354 | 6. Create two Amazon Kinesis Firehose delivery streams: (SPI and DICT) in the AWS Glue Data Catalog to make the conversion in parquet format. Thus, we have the following prefixes and the destination S3 bucket:
355 |
356 | * DICT develivery stream:
357 | * Deliver to S3 Bucket created
358 | * Use the Glue DICT table to convert to PARQUET
359 | * Specify:
360 | ```
361 | prefix: log/dict/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/
362 | errorOutputPrefix: error/dict/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/!{firehose:error-output-type}
363 | ```
364 |
365 | * SPI develivery stream:
366 | * Deliver to S3 Bucket created
367 | * Use the Glue SPI table to convert to PARQUET
368 | * Specify:
369 | ```
370 | prefix: log/spi/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/
371 | errorOutputPrefix: error/spi/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/!{firehose:error-output-type}
372 | ```
373 |
374 | ### AWS Systems Manager Parameter Store
375 |
376 | 1. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/CloudHSMClusterId` and value:
377 | ```
378 |
379 | ```
380 | To find out the CloudHSM Cluster Id is simple. In the AWS console, type CloudHSM and you will find your cluster. In the CloudHSM clusters list you will see the Cluster Id in the format "cluster-xxxxxxxxxxx".
381 |
382 |
383 |
384 |
385 |
386 |
387 | 2. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/CloudHSMCustomerCA` and value:
388 | ```
389 | -----BEGIN CERTIFICATE-----
390 |
391 | -----END CERTIFICATE-----
392 | ```
393 |
394 | 3. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/SignatureKeyLabel` and value:
395 | ```
396 |
397 | ```
398 |
399 | 4. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/SignatureCertificate` and value:
400 | ```
401 | -----BEGIN CERTIFICATE-----
402 |
403 | -----END CERTIFICATE-----
404 | ```
405 |
406 | 5. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/MtlsKeyLabel` and value:
407 | ```
408 |
409 | ```
410 |
411 | 6. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/MtlsCertificate` and value:
412 | ```
413 | -----BEGIN CERTIFICATE-----
414 |
415 | -----END CERTIFICATE-----
416 | ```
417 |
418 | 7. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/DictAuditStream` and value:
419 | ```
420 |
421 | ```
422 |
423 | 8. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/SpiAuditStream` and value:
424 | ```
425 |
426 | ```
427 |
428 | 9. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/BcbDictEndpoint` and value:
429 | ```
430 |
431 | ```
432 | TO USE THE TEST - SIMULATOR, use:
433 | ```
434 | test.pi.rsfn.net.br:8181
435 | ```
436 |
437 | 10. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/BcbSpiEndpoint` and value:
438 | ```
439 |
440 | ```
441 | TO USE THE TEST - SIMULATOR, use:
442 | ```
443 | test.pi.rsfn.net.br:9191
444 | ```
445 |
446 | 11. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/BcbSignatureCertificate` and value:
447 | ```
448 | -----BEGIN CERTIFICATE-----
449 |
450 | -----END CERTIFICATE-----
451 | ```
452 |
453 | TO USE THE TEST - SIMULATOR, use:
454 | ```
455 | -----BEGIN CERTIFICATE-----
456 | MIIDnDCCAoSgAwIBAgIEaLkRBjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJC
457 | UjELMAkGA1UECBMCREYxETAPBgNVBAcTCEJyYXNpbGlhMQwwCgYDVQQKEwNCQ0Ix
458 | DDAKBgNVBAsTA1BJWDEZMBcGA1UEAwwQKi5waS5yc2ZuLm5ldC5icjAeFw0yMDA3
459 | MDUxMzUwMzVaFw0zMDA3MDMxMzUwMzVaMGQxCzAJBgNVBAYTAkJSMQswCQYDVQQI
460 | EwJERjERMA8GA1UEBxMIQnJhc2lsaWExDDAKBgNVBAoTA0JDQjEMMAoGA1UECxMD
461 | UElYMRkwFwYDVQQDDBAqLnBpLnJzZm4ubmV0LmJyMIIBIjANBgkqhkiG9w0BAQEF
462 | AAOCAQ8AMIIBCgKCAQEAy38YHSwphFKHH49rFbl/caqP/ugD0vD3n6lrGzC9xukG
463 | q81bVYXKBzbVtn8gxCOsUCIktMoZNe6QCUeTGshreohIFKdzV/ZH70eZcCOcGoZX
464 | 3evPJuRYIpjjxp0CJbj71EubylavUNpgGjj9v02ezlto94oQN87YR77sDBPBPGeW
465 | CwaYPN8KY0tW8CqrmJXkMsA+pd/1tv3QbBpkUbEgbTvrVTz+9qEUpAg6SeytIulg
466 | icLQrklYPv/Jex4KKcZxAp6SGBrMYmuCViw40qd1SriWk5HYfmMzXSy6DJ7HO5Im
467 | 0F1g43XdEWr0hUmUpsFu2JTIO5qGgx9OcQ6Tw74mgQIDAQABo1YwVDAdBgNVHQ4E
468 | FgQUMswECZ5M0yc1aMkxWdNucYsKjU8wMwYDVR0RAQH/BCkwJ4IJbG9jYWxob3N0
469 | ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
470 | varWSOwcE2A5sIsJbPHczsDXiVOObfJjVol/JBXPH00A8uZ6hbsWDCNp7XZHjheW
471 | snw9acXzKvi+NY/kCYaSegsUr9O+2BBcGhCN4LI5uITE9s3YZKyl+2rqk93P7EDB
472 | RSitjPXeRm9ANPZCR90h+amZQLNbfiK0Povrv61isFqLfdGXnk9B6tfLB+baeS8f
473 | HhxEM22sd+5yo9rUZOdAGI72SMzgaMT1AJZbVbb3z2ymDByJkgTAsVdkkSNkqwEi
474 | Y3lg6cJ5thj5NdaXWc8wCzG6L85uAVV/7eh0SMJ2ITMJwkrrqtrX47LeNPrtCTy/
475 | B8Um+Ao1f9w4nbxP53d+6w==
476 | -----END CERTIFICATE-----
477 | ```
478 |
479 | 12. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/cloudhsm/BcbMtlsCertificate` and value:
480 | ```
481 | -----BEGIN CERTIFICATE-----
482 |
483 | -----END CERTIFICATE-----
484 | ```
485 |
486 | TO USE THE TEST - SIMULATOR, use:
487 | ```
488 | -----BEGIN CERTIFICATE-----
489 | MIIDnDCCAoSgAwIBAgIEBR5HdTANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJC
490 | UjELMAkGA1UECBMCREYxETAPBgNVBAcTCEJyYXNpbGlhMQwwCgYDVQQKEwNCQ0Ix
491 | DDAKBgNVBAsTA1BJWDEZMBcGA1UEAwwQKi5waS5yc2ZuLm5ldC5icjAeFw0yMDA3
492 | MDUxMzUxMTFaFw0zMDA3MDMxMzUxMTFaMGQxCzAJBgNVBAYTAkJSMQswCQYDVQQI
493 | EwJERjERMA8GA1UEBxMIQnJhc2lsaWExDDAKBgNVBAoTA0JDQjEMMAoGA1UECxMD
494 | UElYMRkwFwYDVQQDDBAqLnBpLnJzZm4ubmV0LmJyMIIBIjANBgkqhkiG9w0BAQEF
495 | AAOCAQ8AMIIBCgKCAQEAztOPl4NGjpvf/d07FHHkbJKC7xRwoBhvTpTQ/vQp9E3v
496 | hUI4fIgvvsXAzaknifMysdtk1BRS3Urk6tiL9ZCKEVcqfTPTdawcBi2AABrBvWYx
497 | jDTk5dK1o8wPcUyWRDMRXiWv7grODR75u5a+s3bZTOWLIDxGpY2cuDSRWK0bT8Zh
498 | of+8cn4yML03A83mqrfri1rahH/WpGzwOPk6+pv2m/VKv6GTS1ADD5xTExO5Zotg
499 | wYAuU/zUVZ007CvHGGVoJ87hbUr8EmW1DsgxcPGWeKZ0SzCZkV88eLaD6sedyg0q
500 | 5w1ACRPSK0fRXHpxLkqckJ72hyinr/S/axOvM9bijQIDAQABo1YwVDAdBgNVHQ4E
501 | FgQU+hfJS2Fp0POVlnuOxCsgquNqJocwMwYDVR0RAQH/BCkwJ4IJbG9jYWxob3N0
502 | ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
503 | hjIm6Cj35JR1cbKsIlUFlFVfN7/D9Rx2GOq7JtD4SbzTbDyJ3zm/usVMFFNDNuSs
504 | mJhqLpTKmPX9Akp55RSdnLDEs2tDs7rN5Fy5BODwHblnnyflN0oSihnGop0TtEtv
505 | Gw+zWXms4Pm9Vyi3l+UQA3ENJICP3H7iiPyxj0kThjhFnoIn4kqd+/xSp/BBR6JB
506 | 1UofthomxU4qcYcb4gWBYdgaGzoUIk3W3iMBzQDQmmiqAgKYhEA24SrOgkwWw0/o
507 | 3RNL7mYI5L3tivyrc0/K3/aE0yPhDAMHpC8V7taUwnb4k7rClB0bqF5GmCnWzBis
508 | NAoejbjou87yzYUTY8nRnw==
509 | -----END CERTIFICATE-----
510 | ```
511 |
512 | ### AWS Fargate (PROXY)
513 |
514 | 1. To configure the Amazon ECS using Fargate, use this [procedure](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/getting-started-fargate.html). You can use the dockerfile `proxy/cloudhsm/proxy/src/main/docker/Dockerfile`. You also need configure the following [permissions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) to:
515 |
516 | - Read the secret (AWS Secrets Manager).
517 | - Read the parameters (AWS Systems Manager Parameter Store).
518 | - Put data (log) into deliver streams (Amazon Kinesis Data Firehose).
519 | - [Connect](https://docs.aws.amazon.com/cloudhsm/latest/userguide/configure-sg.html) to the AWS CloudHSM cluster.
520 |
521 | You have to expose the service using **INTERNAL** [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-application-load-balancer.html).
522 |
523 | ### AWS Fargate (TEST - SIMULATOR)
524 |
525 | 1. To configure the Amazon ECS using Fargate for testing, use this [procedure](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/getting-started-fargate.html). You can use the test dockerfile `/proxy/test/src/main/docker/Dockerfile`. You also need configure the following [permissions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) to:
526 |
527 | - Read the parameters (AWS Systems Manager Parameter Store).
528 |
529 | You have to expose the service using the **INTERNAL** [Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/create-network-load-balancer.html).
530 |
531 | 2. You have to configure a [private hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zone-private-creating.html) with domain name `rsfn.net.br`. Also, use this [procedure](https://aws.amazon.com/premiumsupport/knowledge-center/route-53-create-alias-records/) to configure an A record for the name `test.pi.rsfn.net.br` and specify the alias for the TEST Network Load Balancer.
532 |
533 | ### Amazon Athena and Amazon QuickSight
534 |
535 | 1. Use this [procedure](https://docs.aws.amazon.com/athena/latest/ug/getting-started.html) to use Amazon Athena to query data in the S3 bucket created previously.
536 |
537 | 2. Optionally, you can use [Amazon QuickSight](https://docs.aws.amazon.com/quicksight/latest/user/setup-new-quicksight-account.html) that lets you easily create and publish interactive dashboards that include ML Insights. Dashboards can then be accessed from any device, and embedded into your applications, portals, and website.
538 |
--------------------------------------------------------------------------------
/README-KMS.md:
--------------------------------------------------------------------------------
1 | # AWS KMS and AWS Secrets Manager architecture to exemplify digital signature and secure message transmission to the Brazilian Instant Payment System
2 |
3 |
4 |
5 |
6 |
7 | This project contains source code and supporting files that includes the following folders:
8 |
9 | - `proxy/kms` - Proxy that uses AWS KMS.
10 | - `proxy/core` - Sign XML messages.
11 | - `proxy/test` - BACEN simulator.
12 |
13 | The main code of application uses several AWS resources, including AWS KMS and AWS Secrets Manager. The audit part of solution use other AWS resources, including [Amazon Kinesis Firehose](https://aws.amazon.com/kinesis/data-firehose/?nc1=h_ls), [Amazon Athena](https://aws.amazon.com/athena/?nc1=h_ls&whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc), [Amazon S3](https://aws.amazon.com/s3/?nc1=h_ls) and [AWS Glue](https://docs.aws.amazon.com/glue/latest/dg/components-overview.html).
14 |
15 |
16 | ## Following is the proposed architecture
17 |
18 | The architecture presented here can be part of a more complete, [event-based solution](https://aws.amazon.com/en/event-driven-architecture/), which can cover the entire payment message transmission flow, from the banking core. For example, the complete solution of the Financial Institution (paying or receiving), could contain other complementary architectures such as **Authorization**, **Undo** (based on the [SAGA model](https://docs.aws.amazon.com/whitepapers/latest/microservices-on-aws/distributed-data-management.html)), **Effectiveness**, **Communication with on-premises** environment ([hybrid environment](https://aws.amazon.com/en/hybrid/)), etc., using other services such as [Amazon EventBridge](https://aws.amazon.com/en/eventbridge/), Amazon Simple Notification Service ([SNS](https://aws.amazon.com/en/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc)), Amazon Simple Queue Service ([SQS](https://aws.amazon.com/en/sqs/)), [AWS Step Functions](https://aws.amazon.com/en/step-functions/), [Amazon ElastiCache](https://aws.amazon.com/en/elasticache/), [Amazon DynamoDB](https://aws.amazon.com/en/dynamodb/).
19 |
20 | In the diagram of our architecture, the green box represents the Proxy of communication with Central Bank, considering the services AWS KMS, AWS Lambda, Amazon API Gateway and AWS Secrets Manager.
21 |
22 | The idea of the Proxy is to be a direct and mandatory path for every transaction, with the following objectives:
23 |
24 | 1. Signature of XML messages.
25 | 2. Establishment of the TLS tunnel with mutual authentication (mTLS).
26 | 3. Sending the request log to the datastream.
27 |
28 |
29 | Unlike [AWS CloudHSM architecture](https://aws.amazon.com/blogs/industries/supporting-digital-signature-and-message-transmissions-for-brazilian-instant-payment-system-with-aws-cloudhsm/), in which we use ELB to balance SPI and DICT messages, here [private APIs were used](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-apis.html), via AWS API Gateway, to distinguish between the 2 types of messages.
30 |
31 | It is worth mentioning that to use AWS KMS, to sign documents with the requirements required by PIX, it is necessary to generate a Certificate Signing Request (CSR) and, subsequently, obtain a valid digital certificate. To learn how to generate a CSR for asymmetric keys managed by AWS KMS, see [here](xxx).
32 |
33 | Optionally, the Financial Institution can view the transactions that this solution processes using [Amazon QuickSight](https://aws.amazon.com/quicksight/?nc1=h_ls). QuickSight's serverless architecture allows you to provide insights to everyone in your organization, and you can share interactive and sophisticated dashboards with all your users, allowing them to do detailed searches and explore data to answer questions and gain relevant insights.
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 1. Create the private key (for signature) in AWS KMS.
42 | 2. Create the [secret](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_CreateSecret.html) in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc1=h_ls) with the content of the private key used for mTLS.
43 | 3. Store the 2 certificates in the [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html): generated key certificate for signature, certificate generated for the mTLS.
44 | 4. The Financial Institution Service/Application sends the request in XML format.
45 | 5. [Amazon API Gateway](https://aws.amazon.com/api-gateway/?nc1=h_ls) sends the XML message to the application running on [AWS Lambda](https://aws.amazon.com/lambda/?nc1=h_ls).
46 | 6. The application (AWS Lambda) uses AWS KMS for digital signature of XML.
47 | 7. The application (AWS Lambda) uses the private key stored in AWS Secrets Manager to establish the mTLS.
48 | 8. The application transmits the signed XML to BACEN in an encrypted tunnel (mTLS).
49 | 9. Application (AWS Lambda) receives the response from BACEN and, if necessary, validates the digital signature of the received XML.
50 | 10. Application (AWS Lambda) records the request log sending directly to [Amazon Kinesis Data Firehose](https://aws.amazon.com/kinesis/data-firehose/?nc1=h_ls&kinesis-blogs.sort-by=item.additionalFields.createdDate&kinesis-blogs.sort-order=desc).
51 | 11. The reply message is sent to the Amazon API Gateway.
52 | 12. The reply message is received by the Service / Application.
53 | 13. Amazon Kinesis Data Firehose uses the [AWS Glue Data Catalog](https://aws.amazon.com/glue/?nc1=h_ls&whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) to convert the logs to parquet format.
54 | 14. Amazon Kinesis Data Firehose sends the logs to [Amazon S3](https://aws.amazon.com/s3/?nc1=h_ls), already partitioned into “folders” (/year/month/day/hour/).
55 | 15. Amazon Athena uses the [AWS Glue Data Catalog](https://docs.aws.amazon.com/athena/latest/ug/glue-athena.html) as a central place to store and retrieve table metadata.
56 | 16. [AWS Glue crawlers](https://docs.aws.amazon.com/glue/latest/dg/add-crawler.html) automatically update new partitions in the metadata repository, every hour.
57 | 17. You can immediately query the data directly on Amazon S3 using serverless analysis services, such as [Amazon Athena](https://aws.amazon.com/athena/?nc1=h_ls&whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc) (ad hoc with standard SQL) and [Amazon QuickSight](https://aws.amazon.com/quicksight/?nc1=h_ls).
58 |
59 |
60 |
61 | ## How to deploy?
62 |
63 | Here are the resources you’ll need in order to follow along with the architecture:
64 |
65 | * An Amazon Virtual Private Cloud (Amazon VPC) with the following components:
66 | * Private Subnet: AWS Lambda will run on private subnet
67 | * VPC Endpoints: AWS KMS, AWS Secrets Manager, AWS Systems Manager, AWS API Gateway, Amazon Kinesis Firehose
68 |
69 | * Private Key and Certificate for establish the mTLS.
70 |
71 | ### AWS KMS
72 |
73 | 1. [Create an asymmetric key](https://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html#create-asymmetric-cmk):
74 | 1. **Key usage**: Sign and verify
75 | 2. **Key spec**: RSA 2048
76 |
77 | 2. Generate the Certificate:
78 | 1. Please, follow the instructions described in [aws-samples/aws-kms-jce](https://github.com/aws-samples/aws-kms-jce)
79 |
80 | ### AWS Secrets Manager
81 |
82 | [Create](https://docs.aws.amazon.com/secretsmanager/latest/userguide/tutorials_basic.html) a secret with name `/pix/proxy/kms/MtlsPrivateKey` and value of Private Key in PKCS8 format :
83 | ```
84 | -----BEGIN PRIVATE KEY-----
85 | ...
86 | -----END PRIVATE KEY-----
87 | ```
88 |
89 | To convert PKCS8 with OpenSSL (with necessary):
90 | ```
91 | openssl pkcs8 -topk8 -inform PEM -in -out -nocrypt
92 | ```
93 |
94 | ### Register (log audit)
95 |
96 | 1. Before you can upload data to Amazon S3, you must [create a bucket](https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-bucket.html) in one of the AWS Regions to store your data. After you create a bucket, you can upload an unlimited number of data objects to the bucket
97 |
98 | 2. The AWS Glue Data Catalog contains references to data that is used as sources and targets of your extract, transform, and load (ETL) jobs in AWS Glue. Information in the Data Catalog is stored as metadata tables, where each table specifies a [single data store](https://docs.aws.amazon.com/glue/latest/dg/populate-data-catalog.html).
99 |
100 | 3. [Define a database](https://docs.aws.amazon.com/glue/latest/dg/populate-data-catalog.html) in your Data Catalog.
101 |
102 | 4. [Define two tables](https://docs.aws.amazon.com/glue/latest/dg/tables-described.html): SPI and DICT. Both tables need to have the [Columns Structure](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-tables.html#aws-glue-api-catalog-tables-Column) and [Partition Keys](https://docs.aws.amazon.com/glue/latest/dg/tables-described.html#tables-partition), like example below:
103 |
104 | ```
105 | columns: [
106 | {name: 'request_date', type: glue.Schema.STRING},
107 | {name: 'request_method', type: glue.Schema.STRING},
108 | {name: 'request_path', type: glue.Schema.STRING},
109 | {name: 'request_header', type: glue.Schema.STRING},
110 | {name: 'request_body', type: glue.Schema.STRING},
111 | {name: 'response_status_code', type: glue.Schema.INTEGER},
112 | {name: 'response_signature_valid', type: glue.Schema.STRING},
113 | {name: 'response_header', type: glue.Schema.STRING},
114 | {name: 'response_body', type: glue.Schema.STRING}
115 | ],
116 |
117 | partitionKeys: [
118 | {name: 'year', type: glue.Schema.STRING},
119 | {name: 'month', type: glue.Schema.STRING},
120 | {name: 'day', type: glue.Schema.STRING},
121 | {name: 'hour', type: glue.Schema.STRING}
122 | ]
123 | ```
124 |
125 | The DICT table must be pointed to the S3 bucket that you created and must have the prefix `log/dict`.
126 |
127 | The SPI table must be pointed to the S3 bucket that you created and must have the prefix `log/spi`.
128 |
129 | 5. [Create a crawler](https://docs.aws.amazon.com/glue/latest/dg/add-crawler.html) with target to the created tables (SPI and DICT).
130 |
131 | 6. Create two Amazon Kinesis Firehose delivery streams: (SPI and DICT) in the AWS Glue Data Catalog to make the conversion in parquet format. Thus, we have the following prefixes and the destination S3 bucket:
132 |
133 | * DICT develivery stream:
134 | * Deliver to S3 Bucket created
135 | * Use the Glue DICT table to convert to PARQUET
136 | * Specify:
137 | ```
138 | prefix: log/dict/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/
139 | errorOutputPrefix: error/dict/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/!{firehose:error-output-type}
140 | ```
141 |
142 | * SPI develivery stream:
143 | * Deliver to S3 Bucket created
144 | * Use the Glue SPI table to convert to PARQUET
145 | * Specify:
146 | ```
147 | prefix: log/spi/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/
148 | errorOutputPrefix: error/spi/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/!{firehose:error-output-type}
149 | ```
150 |
151 | ### AWS Systems Manager Parameter Store
152 |
153 | 1. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/SignatureKeyId` and value:
154 | ```
155 |
156 | ```
157 |
158 | 2. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/SignatureCertificate` and value:
159 | ```
160 | -----BEGIN CERTIFICATE-----
161 |
162 | -----END CERTIFICATE-----
163 | ```
164 |
165 | 3. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/MtlsCertificate` and value:
166 | ```
167 | -----BEGIN CERTIFICATE-----
168 |
169 | -----END CERTIFICATE-----
170 | ```
171 |
172 | 4. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/DictAuditStream` and value:
173 | ```
174 |
175 | ```
176 |
177 | 5. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/SpiAuditStream` and value:
178 | ```
179 |
180 | ```
181 |
182 | 6. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/BcbDictEndpoint` and value:
183 | ```
184 |
185 | ```
186 | TO USE THE TEST - SIMULATOR, use:
187 | ```
188 | test.pi.rsfn.net.br:8181
189 | ```
190 |
191 | 7. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/BcbSpiEndpoint` and value:
192 | ```
193 |
194 | ```
195 | TO USE THE TEST - SIMULATOR, use:
196 | ```
197 | test.pi.rsfn.net.br:9191
198 | ```
199 |
200 | 8. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/BcbSignatureCertificate` and value:
201 | ```
202 | -----BEGIN CERTIFICATE-----
203 |
204 | -----END CERTIFICATE-----
205 | ```
206 |
207 | TO USE THE TEST - SIMULATOR, use:
208 | ```
209 | -----BEGIN CERTIFICATE-----
210 | MIIDnDCCAoSgAwIBAgIEaLkRBjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJC
211 | UjELMAkGA1UECBMCREYxETAPBgNVBAcTCEJyYXNpbGlhMQwwCgYDVQQKEwNCQ0Ix
212 | DDAKBgNVBAsTA1BJWDEZMBcGA1UEAwwQKi5waS5yc2ZuLm5ldC5icjAeFw0yMDA3
213 | MDUxMzUwMzVaFw0zMDA3MDMxMzUwMzVaMGQxCzAJBgNVBAYTAkJSMQswCQYDVQQI
214 | EwJERjERMA8GA1UEBxMIQnJhc2lsaWExDDAKBgNVBAoTA0JDQjEMMAoGA1UECxMD
215 | UElYMRkwFwYDVQQDDBAqLnBpLnJzZm4ubmV0LmJyMIIBIjANBgkqhkiG9w0BAQEF
216 | AAOCAQ8AMIIBCgKCAQEAy38YHSwphFKHH49rFbl/caqP/ugD0vD3n6lrGzC9xukG
217 | q81bVYXKBzbVtn8gxCOsUCIktMoZNe6QCUeTGshreohIFKdzV/ZH70eZcCOcGoZX
218 | 3evPJuRYIpjjxp0CJbj71EubylavUNpgGjj9v02ezlto94oQN87YR77sDBPBPGeW
219 | CwaYPN8KY0tW8CqrmJXkMsA+pd/1tv3QbBpkUbEgbTvrVTz+9qEUpAg6SeytIulg
220 | icLQrklYPv/Jex4KKcZxAp6SGBrMYmuCViw40qd1SriWk5HYfmMzXSy6DJ7HO5Im
221 | 0F1g43XdEWr0hUmUpsFu2JTIO5qGgx9OcQ6Tw74mgQIDAQABo1YwVDAdBgNVHQ4E
222 | FgQUMswECZ5M0yc1aMkxWdNucYsKjU8wMwYDVR0RAQH/BCkwJ4IJbG9jYWxob3N0
223 | ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
224 | varWSOwcE2A5sIsJbPHczsDXiVOObfJjVol/JBXPH00A8uZ6hbsWDCNp7XZHjheW
225 | snw9acXzKvi+NY/kCYaSegsUr9O+2BBcGhCN4LI5uITE9s3YZKyl+2rqk93P7EDB
226 | RSitjPXeRm9ANPZCR90h+amZQLNbfiK0Povrv61isFqLfdGXnk9B6tfLB+baeS8f
227 | HhxEM22sd+5yo9rUZOdAGI72SMzgaMT1AJZbVbb3z2ymDByJkgTAsVdkkSNkqwEi
228 | Y3lg6cJ5thj5NdaXWc8wCzG6L85uAVV/7eh0SMJ2ITMJwkrrqtrX47LeNPrtCTy/
229 | B8Um+Ao1f9w4nbxP53d+6w==
230 | -----END CERTIFICATE-----
231 | ```
232 |
233 | 9. [Create](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-su-create.html) a parameter `/pix/proxy/kms/BcbMtlsCertificate` and value:
234 | ```
235 | -----BEGIN CERTIFICATE-----
236 |
237 | -----END CERTIFICATE-----
238 | ```
239 |
240 | TO USE THE TEST - SIMULATOR, use:
241 | ```
242 | -----BEGIN CERTIFICATE-----
243 | MIIDnDCCAoSgAwIBAgIEBR5HdTANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJC
244 | UjELMAkGA1UECBMCREYxETAPBgNVBAcTCEJyYXNpbGlhMQwwCgYDVQQKEwNCQ0Ix
245 | DDAKBgNVBAsTA1BJWDEZMBcGA1UEAwwQKi5waS5yc2ZuLm5ldC5icjAeFw0yMDA3
246 | MDUxMzUxMTFaFw0zMDA3MDMxMzUxMTFaMGQxCzAJBgNVBAYTAkJSMQswCQYDVQQI
247 | EwJERjERMA8GA1UEBxMIQnJhc2lsaWExDDAKBgNVBAoTA0JDQjEMMAoGA1UECxMD
248 | UElYMRkwFwYDVQQDDBAqLnBpLnJzZm4ubmV0LmJyMIIBIjANBgkqhkiG9w0BAQEF
249 | AAOCAQ8AMIIBCgKCAQEAztOPl4NGjpvf/d07FHHkbJKC7xRwoBhvTpTQ/vQp9E3v
250 | hUI4fIgvvsXAzaknifMysdtk1BRS3Urk6tiL9ZCKEVcqfTPTdawcBi2AABrBvWYx
251 | jDTk5dK1o8wPcUyWRDMRXiWv7grODR75u5a+s3bZTOWLIDxGpY2cuDSRWK0bT8Zh
252 | of+8cn4yML03A83mqrfri1rahH/WpGzwOPk6+pv2m/VKv6GTS1ADD5xTExO5Zotg
253 | wYAuU/zUVZ007CvHGGVoJ87hbUr8EmW1DsgxcPGWeKZ0SzCZkV88eLaD6sedyg0q
254 | 5w1ACRPSK0fRXHpxLkqckJ72hyinr/S/axOvM9bijQIDAQABo1YwVDAdBgNVHQ4E
255 | FgQU+hfJS2Fp0POVlnuOxCsgquNqJocwMwYDVR0RAQH/BCkwJ4IJbG9jYWxob3N0
256 | ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
257 | hjIm6Cj35JR1cbKsIlUFlFVfN7/D9Rx2GOq7JtD4SbzTbDyJ3zm/usVMFFNDNuSs
258 | mJhqLpTKmPX9Akp55RSdnLDEs2tDs7rN5Fy5BODwHblnnyflN0oSihnGop0TtEtv
259 | Gw+zWXms4Pm9Vyi3l+UQA3ENJICP3H7iiPyxj0kThjhFnoIn4kqd+/xSp/BBR6JB
260 | 1UofthomxU4qcYcb4gWBYdgaGzoUIk3W3iMBzQDQmmiqAgKYhEA24SrOgkwWw0/o
261 | 3RNL7mYI5L3tivyrc0/K3/aE0yPhDAMHpC8V7taUwnb4k7rClB0bqF5GmCnWzBis
262 | NAoejbjou87yzYUTY8nRnw==
263 | -----END CERTIFICATE-----
264 | ```
265 |
266 | ### AWS Lambda
267 |
268 | 1. Generate the deployment package (function.zip)
269 | ```
270 | mvn -f proxy/pom.xml -pl core,kms clean package -DskipTests -Pnative -Dnative-image.docker-build=true
271 | ```
272 |
273 | 2. Create Lambda for SPI:
274 | ```
275 | runtime: PROVIDED
276 | handler: 'none'
277 | code: function.zip
278 | memorySize: 1024
279 | timeout: 30 sec
280 | vpc:
281 | environment variables:
282 | - DISABLE_SIGNAL_HANDLERS: true
283 | - PIX_SPI_PROXY: true
284 | ```
285 |
286 | 3. Create Lambda for DICT:
287 | ```
288 | runtime: PROVIDED
289 | handler: 'none'
290 | code: function.zip
291 | memorySize: 1024
292 | timeout: 30 sec
293 | vpc:
294 | environment variables:
295 | - DISABLE_SIGNAL_HANDLERS: true
296 | - PIX_SPI_PROXY: false
297 | ```
298 |
299 | 4. You also need configure the following [permissions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) to:
300 | - Read the secret (AWS Secrets Manager).
301 | - Read the parameters (AWS Systems Manager Parameter Store).
302 | - Sign documents (AWS KMS).
303 | - Put data (log) into deliver streams (Amazon Kinesis Data Firehose).
304 |
305 | ### AWS API Gateway
306 |
307 | 1. Create a **proxy internal** API for SPI. [Check here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html).
308 | 2. Create a **proxy internal** API for DICT. [Check here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html).
309 |
310 | ### AWS Fargate (TEST - SIMULATOR)
311 |
312 | 1. To configure the Amazon ECS using Fargate for testing, use this [procedure](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/getting-started-fargate.html). You can use the test dockerfile `/proxy/test/src/main/docker/Dockerfile`. You also need configure the following [permissions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html) to:
313 |
314 | - Read the parameters (AWS Systems Manager Parameter Store).
315 |
316 | You have to expose the service using the **INTERNAL** [Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/create-network-load-balancer.html).
317 |
318 | 2. You have to configure a [private hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zone-private-creating.html) with domain name `rsfn.net.br`. Also, use this [procedure](https://aws.amazon.com/premiumsupport/knowledge-center/route-53-create-alias-records/) to configure an A record for the name `test.pi.rsfn.net.br` and specify the alias for the TEST Network Load Balancer.
319 |
320 | ### Amazon Athena and Amazon QuickSight
321 |
322 | 1. Use this [procedure](https://docs.aws.amazon.com/athena/latest/ug/getting-started.html) to use Amazon Athena to query data in the S3 bucket created previously.
323 |
324 | 2. Optionally, you can use [Amazon QuickSight](https://docs.aws.amazon.com/quicksight/latest/user/setup-new-quicksight-account.html) that lets you easily create and publish interactive dashboards that include ML Insights. Dashboards can then be accessed from any device, and embedded into your applications, portals, and website.
325 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Architectures to exemplify digital signature and secure message transmission to the Brazilian Instant Payment System (PIX)
2 |
3 | ### ***You can clone, change, execute it, but *it should not be used as a basis for building the final integration* of the Financial Institution with PIX (SPI and DICT).***
4 |
5 | This project contains source code and supporting files to exemplify digital signature and secure message transmission to the Brazilian Instant Payment System (PIX). The architectures represent a **proxy** for communication with Brazilian Central Bank (BACEN). The idea of the proxy is to use **AWS CloudHSM** as a direct and mandatory path for every transaction, with the following objectives:
6 |
7 | ```bash
8 | - Establish the TLS tunnel with mutual authentication (mTLS).
9 | - Signature of XML messages.
10 | - Sending the request log to the datastream.
11 | ```
12 |
13 | AWS CloudHSM | AWS KMS |
14 | :-:|:-:|
15 | | |
16 | [Click here!](README-CloudHSM.md)|[Click here!](README-KMS.md)|
17 |
18 | ## Security
19 |
20 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
21 |
22 | ## License
23 |
24 | This library is licensed under the MIT-0 License. See the LICENSE file.
25 |
--------------------------------------------------------------------------------
/images/cloudhsm-id.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/cloudhsm-id.jpg
--------------------------------------------------------------------------------
/images/hsm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/hsm.jpg
--------------------------------------------------------------------------------
/images/kms.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/kms.jpg
--------------------------------------------------------------------------------
/images/proxy-cloudhsm-arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/proxy-cloudhsm-arch.png
--------------------------------------------------------------------------------
/images/proxy-cloudhsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/proxy-cloudhsm.png
--------------------------------------------------------------------------------
/images/proxy-kms-arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/proxy-kms-arch.png
--------------------------------------------------------------------------------
/images/proxy-kms-arch1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/images/proxy-kms-arch1.png
--------------------------------------------------------------------------------
/proxy/cloudhsm/cavium/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 | pix-cloudhsm-cavium
6 | 1.0.0
7 | pom
8 |
9 | CloudHSM Cavium JCE
10 |
11 |
12 | com.amazon.aws
13 | pix-cloudhsm
14 | 1.0.0
15 |
16 |
17 |
18 |
19 |
20 | com.googlecode.maven-download-plugin
21 | download-maven-plugin
22 | 1.5.1
23 |
24 |
25 | cloudhsm-client-jce-latest-download
26 |
27 | wget
28 |
29 | generate-resources
30 |
31 | true
32 | https://s3.amazonaws.com/cloudhsmv2-software/CloudHsmClient/EL7/cloudhsm-client-jce-latest.el7.x86_64.rpm
33 | ${project.build.directory}/cloudhsm-client-jce
34 |
35 |
36 |
37 |
38 |
39 | org.codehaus.mojo
40 | rpm-maven-plugin
41 | 2.2.0
42 |
43 |
44 | cloudhsm-client-jce-latest-unpack
45 |
46 | unpack
47 |
48 | generate-resources
49 |
50 | ${project.build.directory}/cloudhsm-client-jce/cloudhsm-client-jce-latest.el7.x86_64.rpm
51 | ${project.build.directory}/cloudhsm-client-jce
52 |
53 |
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-antrun-plugin
59 | 3.0.0
60 |
61 |
62 | export-cloudhsm-jce-version
63 | generate-resources
64 |
65 | run
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | true
81 |
82 |
83 |
84 |
85 |
86 | org.apache.maven.plugins
87 | maven-install-plugin
88 | 2.5.2
89 |
90 |
91 | install-cloudhsm-jce
92 |
93 | install-file
94 |
95 | generate-resources
96 |
97 | com.cavium
98 | cloudhsm
99 | ${cloudhsm.version}
100 | jar
101 | ${project.build.directory}/cloudhsm-client-jce/opt/cloudhsm/java/cloudhsm-${cloudhsm.version}.jar
102 | true
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | pix-cloudhsm
5 | pom
6 |
7 | Pix CloudHSM Parent POM
8 |
9 |
10 | com.amazon.aws
11 | pix
12 | 1.0.0
13 |
14 |
15 |
16 | cavium
17 | proxy
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 | pix-cloudhsm-proxy
6 | jar
7 |
8 | PIX CloudHSM Proxy
9 |
10 |
11 | com.amazon.aws
12 | pix-cloudhsm
13 | 1.0.0
14 |
15 |
16 |
17 |
18 | com.amazon.aws
19 | pix-core
20 |
21 |
22 |
23 | software.amazon.awssdk
24 | secretsmanager
25 |
26 |
27 | software.amazon.awssdk
28 | netty-nio-client
29 |
30 |
31 | software.amazon.awssdk
32 | apache-client
33 |
34 |
35 |
36 |
37 |
38 | software.amazon.awssdk
39 | ssm
40 |
41 |
42 | software.amazon.awssdk
43 | netty-nio-client
44 |
45 |
46 | software.amazon.awssdk
47 | apache-client
48 |
49 |
50 |
51 |
52 |
53 | software.amazon.awssdk
54 | firehose
55 |
56 |
57 | software.amazon.awssdk
58 | netty-nio-client
59 |
60 |
61 | software.amazon.awssdk
62 | apache-client
63 |
64 |
65 |
66 |
67 |
68 | software.amazon.awssdk
69 | url-connection-client
70 |
71 |
72 |
73 | com.cavium
74 | cloudhsm
75 | [3.0.0,)
76 |
77 |
78 | org.apache.logging.log4j
79 | log4j-core
80 | 2.17.1
81 |
82 |
83 |
84 | org.projectlombok
85 | lombok
86 |
87 |
88 |
89 | org.apache.camel.quarkus
90 | camel-quarkus-netty-http
91 |
92 |
93 |
94 | io.netty
95 | netty-transport-native-epoll
96 | linux-x86_64
97 |
98 |
99 |
100 | io.netty
101 | netty-tcnative
102 | 2.0.31.Final
103 | linux-x86_64-fedora
104 |
105 |
106 |
107 |
108 |
109 |
110 | io.quarkus
111 | quarkus-maven-plugin
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM amazoncorretto:11
2 |
3 | WORKDIR /tmp/
4 | RUN curl https://s3.amazonaws.com/cloudhsmv2-software/CloudHsmClient/EL7/cloudhsm-client-latest.el7.x86_64.rpm -O \
5 | && curl https://s3.amazonaws.com/cloudhsmv2-software/CloudHsmClient/EL7/cloudhsm-client-jce-latest.el7.x86_64.rpm -O \
6 | && curl https://s3.amazonaws.com/cloudhsmv2-software/CloudHsmClient/EL7/cloudhsm-client-dyn-latest.el7.x86_64.rpm -O
7 |
8 | RUN yum -y install ./cloudhsm-client-latest.el7.x86_64.rpm \
9 | ./cloudhsm-client-jce-latest.el7.x86_64.rpm \
10 | awscli openssl apr \
11 | && yum clean all \
12 | && rm -rf /var/cache/yum
13 |
14 | RUN chown -R 1001 /opt/cloudhsm \
15 | && chmod -R "g+rwX" /opt/cloudhsm \
16 | && chown -R 1001:root /opt/cloudhsm
17 |
18 | ENV PATH="/opt/cloudhsm/bin:${PATH}"
19 | ENV LD_LIBRARY_PATH="/opt/cloudhsm/lib"
20 |
21 | WORKDIR /work/
22 | COPY proxy/cloudhsm/proxy/src/main/docker/wrapper_script.sh /work/wrapper_script.sh
23 | COPY proxy/cloudhsm/proxy/target/*-runner.jar /work/application.jar
24 |
25 | # set up permissions for user `1001`
26 | RUN chmod 775 /work /work/wrapper_script.sh \
27 | && chown -R 1001 /work \
28 | && chmod -R "g+rwX" /work \
29 | && chown -R 1001:root /work
30 |
31 | EXPOSE 8080
32 | EXPOSE 9090
33 | USER 1001
34 |
35 | CMD ["./wrapper_script.sh"]
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/docker/wrapper_script.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Start CloudHSM Client
4 | echo "$HSM_CUSTOMER_CA" > /opt/cloudhsm/etc/customerCA.crt
5 | HSM_IP=`aws cloudhsmv2 describe-clusters --region $AWS_DEFAULT_REGION --filter clusterIds=$HSM_CLUSTER_ID --query "Clusters[0].Hsms[0].EniIp" --output text`
6 | /opt/cloudhsm/bin/configure -a $HSM_IP
7 | /opt/cloudhsm/bin/cloudhsm_client /opt/cloudhsm/etc/cloudhsm_client.cfg &> /tmp/cloudhsm_client_start.log &
8 | # wait for startup
9 | while true
10 | do
11 | if grep 'libevmulti_init: Ready !' /tmp/cloudhsm_client_start.log &> /dev/null
12 | then
13 | echo "[OK]"
14 | break
15 | fi
16 | sleep 0.5
17 | done
18 | echo -e "\n* CloudHSM client started successfully ... \n"
19 |
20 | echo "safe sleeping... 10s"
21 | sleep 10
22 | echo "awake and resuming..."
23 |
24 | # Start application
25 | java -jar application.jar
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/PixCloudHSMProxyRouteBuilder.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy;
2 |
3 | import com.amazon.aws.pix.cloudhsm.proxy.camel.netty.NettyHttpClientInitializerFactory;
4 | import com.amazon.aws.pix.cloudhsm.proxy.camel.netty.NettySSLContextParameters;
5 | import com.amazon.aws.pix.cloudhsm.proxy.processor.CaptureRequestProcessor;
6 | import com.amazon.aws.pix.cloudhsm.proxy.processor.LogRequestResponseProcessor;
7 | import com.amazon.aws.pix.cloudhsm.proxy.processor.SignRequestProcessor;
8 | import com.amazon.aws.pix.cloudhsm.proxy.processor.VerifyResponseProcessor;
9 | import com.amazon.aws.pix.core.util.KeyStoreUtil;
10 | import com.amazon.aws.pix.core.xml.Iso20022XmlSigner;
11 | import com.amazon.aws.pix.core.xml.XmlSigner;
12 | import com.cavium.cfm2.CFM2Exception;
13 | import com.cavium.cfm2.LoginManager;
14 | import io.netty.handler.ssl.SslContext;
15 | import io.netty.handler.ssl.SslContextBuilder;
16 | import io.netty.handler.ssl.SslProvider;
17 | import lombok.AllArgsConstructor;
18 | import org.apache.camel.builder.EndpointConsumerBuilder;
19 | import org.apache.camel.builder.EndpointProducerBuilder;
20 | import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
21 | import org.eclipse.microprofile.config.inject.ConfigProperty;
22 | import org.json.JSONObject;
23 | import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
24 | import software.amazon.awssdk.regions.Region;
25 | import software.amazon.awssdk.services.firehose.FirehoseClient;
26 | import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
27 | import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
28 | import software.amazon.awssdk.services.ssm.SsmClient;
29 | import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest;
30 | import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse;
31 | import software.amazon.awssdk.services.ssm.model.Parameter;
32 |
33 | import javax.annotation.PostConstruct;
34 | import javax.enterprise.context.ApplicationScoped;
35 | import javax.net.ssl.SSLException;
36 | import java.io.IOException;
37 | import java.security.*;
38 | import java.security.cert.CertificateException;
39 | import java.security.cert.X509Certificate;
40 | import java.util.Collection;
41 | import java.util.HashMap;
42 | import java.util.Map;
43 | import java.util.Optional;
44 | import java.util.stream.Collectors;
45 |
46 | @ApplicationScoped
47 | public class PixCloudHSMProxyRouteBuilder extends EndpointRouteBuilder {
48 |
49 | @ConfigProperty(name = "aws.default.region")
50 | String awsDefaultRegion;
51 |
52 | @AllArgsConstructor
53 | enum Secret {
54 | CloudHSMSecret("HSM_USER", "HSM_PASSWORD");
55 |
56 | public final String user;
57 | public final String password;
58 |
59 | public String getSecretId() {
60 | return String.format("/pix/proxy/cloudhsm/%s", this.name());
61 | }
62 | }
63 |
64 | enum Param {
65 | CloudHSMClusterId,
66 | CloudHSMCustomerCA,
67 | MtlsKeyLabel,
68 | MtlsCertificate,
69 | SignatureKeyLabel,
70 | SignatureCertificate,
71 | BcbMtlsCertificate,
72 | BcbSignatureCertificate,
73 | BcbDictEndpoint,
74 | BcbSpiEndpoint,
75 | SpiAuditStream,
76 | DictAuditStream;
77 |
78 | public static final String PATH = "/pix/proxy/cloudhsm/";
79 |
80 | public String getParamName() {
81 | return String.format("%s%s", PATH, this.name());
82 | }
83 | }
84 |
85 | private Map parameters;
86 | private KeyStore cloudHsmKeyStore;
87 | private SslContext sslContext;
88 | private XmlSigner xmlSigner;
89 | private Iso20022XmlSigner iso20022XmlSigner;
90 | private FirehoseClient firehoseClient;
91 |
92 | @PostConstruct
93 | void init() throws Exception {
94 | loadParameters();
95 | loadCloudHsmKeyStore();
96 | createSslContext();
97 | createXmlSigners();
98 | createFirehoseClient();
99 | }
100 |
101 | @Override
102 | public void configure() throws Exception {
103 | getContext().getRegistry().bind("nettyHttpClientInitializerFactory", new NettyHttpClientInitializerFactory());
104 |
105 | configure(8080, xmlSigner, getParameter(Param.BcbDictEndpoint), getParameter(Param.DictAuditStream));
106 | configure(9090, iso20022XmlSigner, getParameter(Param.BcbSpiEndpoint), getParameter(Param.SpiAuditStream));
107 |
108 | from(checkEndpoint()).transform(constant("OK"));
109 | }
110 |
111 | private void configure(int port, XmlSigner xmlSigner, String endpoint, String streamName) {
112 | from(proxyEndpoint(port))
113 | .transform(body().convertToString())
114 | .process(new SignRequestProcessor(xmlSigner))
115 | .process(new CaptureRequestProcessor())
116 | .to(bcbEndpoint(endpoint))
117 | .transform(body().convertToString())
118 | .process(new VerifyResponseProcessor(xmlSigner))
119 | .process(new LogRequestResponseProcessor(firehoseClient, streamName));
120 |
121 | }
122 |
123 | private EndpointConsumerBuilder proxyEndpoint(int port) {
124 | return nettyHttp(String.format("http://0.0.0.0:%d", port))
125 | .matchOnUriPrefix(true)
126 | .advanced().nativeTransport(true);
127 | }
128 |
129 | private EndpointProducerBuilder bcbEndpoint(String endpoint) {
130 | NettySSLContextParameters nettySSLContextParameters = new NettySSLContextParameters();
131 | nettySSLContextParameters.setSslContext(sslContext);
132 |
133 | return nettyHttp("https://" + endpoint)
134 | .bridgeEndpoint(true)
135 | .throwExceptionOnFailure(false)
136 | .ssl(true)
137 | .enabledProtocols("TLSv1.2")
138 | .sslContextParameters(nettySSLContextParameters)
139 | .advanced().nativeTransport(true);
140 | }
141 |
142 | private EndpointConsumerBuilder checkEndpoint() {
143 | return nettyHttp("http://0.0.0.0:7070/check")
144 | .advanced().nativeTransport(true);
145 | }
146 |
147 | private void loadParameters() {
148 | SsmClient ssmClient = SsmClient.builder()
149 | .region(Region.of(awsDefaultRegion))
150 | .httpClientBuilder(UrlConnectionHttpClient.builder())
151 | .build();
152 |
153 | parameters = new HashMap<>();
154 | String nextToken = null;
155 | do {
156 | GetParametersByPathResponse response = ssmClient.getParametersByPath(GetParametersByPathRequest.builder().nextToken(nextToken).path(Param.PATH).recursive(true).build());
157 | parameters.putAll(response.parameters().stream().collect(Collectors.toMap(Parameter::name, Parameter::value)));
158 | nextToken = response.nextToken();
159 | } while (nextToken != null);
160 | }
161 |
162 | private void loadCloudHsmKeyStore() throws IOException, CFM2Exception, KeyStoreException, CertificateException, NoSuchAlgorithmException {
163 |
164 | SecretsManagerClient secretsManagerClient = SecretsManagerClient.builder()
165 | .region(Region.of(awsDefaultRegion))
166 | .httpClientBuilder(UrlConnectionHttpClient.builder())
167 | .build();
168 |
169 | GetSecretValueResponse secretValue = secretsManagerClient.getSecretValue(builder -> builder.secretId(Secret.CloudHSMSecret.getSecretId()));
170 | JSONObject secret = new JSONObject(secretValue.secretString());
171 | String hsmUser = secret.getString(Secret.CloudHSMSecret.user);
172 | String hsmPassword = secret.getString(Secret.CloudHSMSecret.password);
173 |
174 | Security.addProvider(new com.cavium.provider.CaviumProvider());
175 | LoginManager.getInstance().login("PARTITION_1", hsmUser, hsmPassword);
176 | cloudHsmKeyStore = KeyStore.getInstance("CloudHSM");
177 | cloudHsmKeyStore.load(null, null);
178 | }
179 |
180 | private void createSslContext() throws SSLException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
181 | PrivateKey signatureKey = (PrivateKey) cloudHsmKeyStore.getKey(getParameter(Param.MtlsKeyLabel), null);
182 | Collection certificates = KeyStoreUtil.getCertificates(getParameter(Param.MtlsCertificate));
183 | Collection trustCertificates = KeyStoreUtil.getCertificates(getParameter(Param.BcbMtlsCertificate));
184 |
185 | sslContext = SslContextBuilder.forClient()
186 | .sslProvider(SslProvider.OPENSSL)
187 | .keyManager(signatureKey, certificates)
188 | .trustManager(trustCertificates)
189 | .protocols("TLSv1.2")
190 | .build();
191 | }
192 |
193 | private void createXmlSigners() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
194 | PrivateKey signatureKey = (PrivateKey) cloudHsmKeyStore.getKey(getParameter(Param.SignatureKeyLabel), null);
195 | X509Certificate signatureKeyCertificate = KeyStoreUtil.getCertificate(getParameter(Param.SignatureCertificate));
196 | KeyStore trustStore = KeyStoreUtil.generateTrustStore("bcb", getParameter(Param.BcbSignatureCertificate));
197 |
198 | xmlSigner = new XmlSigner(signatureKey, signatureKeyCertificate, trustStore);
199 | iso20022XmlSigner = new Iso20022XmlSigner(signatureKey, signatureKeyCertificate, trustStore);
200 | }
201 |
202 | private void createFirehoseClient() {
203 | firehoseClient = FirehoseClient.builder()
204 | .region(Region.of(awsDefaultRegion))
205 | .httpClientBuilder(UrlConnectionHttpClient.builder())
206 | .build();
207 | }
208 |
209 | private String getParameter(Param param) {
210 | return Optional.ofNullable(parameters.get(param.getParamName()))
211 | .orElseThrow(() -> new IllegalStateException(String.format("Parameter %s not found!", param.getParamName())));
212 | }
213 |
214 | }
215 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/camel/netty/NettyHttpClientInitializerFactory.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy.camel.netty;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelHandler;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.handler.codec.http.HttpClientCodec;
7 | import io.netty.handler.codec.http.HttpObjectAggregator;
8 | import io.netty.handler.ssl.SslContext;
9 | import io.netty.handler.ssl.SslHandler;
10 | import io.netty.handler.timeout.ReadTimeoutHandler;
11 | import org.apache.camel.RuntimeCamelException;
12 | import org.apache.camel.component.netty.ChannelHandlerFactory;
13 | import org.apache.camel.component.netty.ClientInitializerFactory;
14 | import org.apache.camel.component.netty.NettyConfiguration;
15 | import org.apache.camel.component.netty.NettyProducer;
16 | import org.apache.camel.component.netty.http.NettyHttpConfiguration;
17 | import org.apache.camel.component.netty.http.NettyHttpProducer;
18 | import org.apache.camel.component.netty.http.handlers.HttpClientChannelHandler;
19 | import org.apache.camel.component.netty.http.handlers.HttpInboundStreamHandler;
20 | import org.apache.camel.component.netty.http.handlers.HttpOutboundStreamHandler;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | import javax.net.ssl.SNIHostName;
25 | import javax.net.ssl.SSLEngine;
26 | import javax.net.ssl.SSLParameters;
27 | import java.net.URI;
28 | import java.util.Arrays;
29 | import java.util.List;
30 | import java.util.concurrent.TimeUnit;
31 |
32 | public class NettyHttpClientInitializerFactory extends ClientInitializerFactory {
33 |
34 | private static final Logger LOG = LoggerFactory.getLogger(NettyHttpClientInitializerFactory.class);
35 | protected NettyHttpConfiguration configuration;
36 | private NettyHttpProducer producer;
37 | private SslContext sslContext;
38 |
39 | public NettyHttpClientInitializerFactory() {
40 | // default constructor needed
41 | }
42 |
43 | public NettyHttpClientInitializerFactory(NettyHttpProducer nettyProducer) {
44 | this.producer = nettyProducer;
45 | try {
46 | this.sslContext = createSSLContext(producer);
47 | } catch (Exception e) {
48 | throw RuntimeCamelException.wrapRuntimeCamelException(e);
49 | }
50 |
51 | if (sslContext != null) {
52 | LOG.info("Created SslContext {}", sslContext);
53 | }
54 | configuration = nettyProducer.getConfiguration();
55 | }
56 |
57 | @Override
58 | public ClientInitializerFactory createPipelineFactory(NettyProducer nettyProducer) {
59 | return new NettyHttpClientInitializerFactory((NettyHttpProducer) nettyProducer);
60 | }
61 |
62 | @Override
63 | protected void initChannel(Channel channel) throws Exception {
64 | // create a new pipeline
65 | ChannelPipeline pipeline = channel.pipeline();
66 |
67 | SslHandler sslHandler = configureClientSSLOnDemand(channel);
68 | if (sslHandler != null) {
69 | //TODO must close on SSL exception
70 | //sslHandler.setCloseOnSSLException(true);
71 | LOG.debug("Client SSL handler configured and added as an interceptor against the ChannelPipeline: {}", sslHandler);
72 | pipeline.addLast("ssl", sslHandler);
73 | }
74 |
75 | pipeline.addLast("http", new HttpClientCodec());
76 |
77 | List encoders = producer.getConfiguration().getEncoders();
78 | for (int x = 0; x < encoders.size(); x++) {
79 | ChannelHandler encoder = encoders.get(x);
80 | if (encoder instanceof ChannelHandlerFactory) {
81 | // use the factory to create a new instance of the channel as it may not be shareable
82 | encoder = ((ChannelHandlerFactory) encoder).newChannelHandler();
83 | }
84 | pipeline.addLast("encoder-" + x, encoder);
85 | }
86 |
87 | List decoders = producer.getConfiguration().getDecoders();
88 | for (int x = 0; x < decoders.size(); x++) {
89 | ChannelHandler decoder = decoders.get(x);
90 | if (decoder instanceof ChannelHandlerFactory) {
91 | // use the factory to create a new instance of the channel as it may not be shareable
92 | decoder = ((ChannelHandlerFactory) decoder).newChannelHandler();
93 | }
94 | pipeline.addLast("decoder-" + x, decoder);
95 | }
96 | if (configuration.isDisableStreamCache()) {
97 | pipeline.addLast("inbound-streamer", new HttpInboundStreamHandler());
98 | }
99 | pipeline.addLast("aggregator", new HttpObjectAggregator(configuration.getChunkedMaxContentLength()));
100 | pipeline.addLast("outbound-streamer", new HttpOutboundStreamHandler());
101 |
102 | if (producer.getConfiguration().getRequestTimeout() > 0) {
103 | if (LOG.isTraceEnabled()) {
104 | LOG.trace("Using request timeout {} millis", producer.getConfiguration().getRequestTimeout());
105 | }
106 | ChannelHandler timeout = new ReadTimeoutHandler(producer.getConfiguration().getRequestTimeout(), TimeUnit.MILLISECONDS);
107 | pipeline.addLast("timeout", timeout);
108 | }
109 |
110 | // handler to route Camel messages
111 | pipeline.addLast("handler", new HttpClientChannelHandler(producer));
112 | }
113 |
114 | protected SslContext createSSLContext(NettyProducer producer) throws Exception {
115 | NettyConfiguration configuration = producer.getConfiguration();
116 |
117 | if (!configuration.isSsl()) {
118 | return null;
119 | }
120 |
121 | if (configuration.getSslContextParameters() != null) {
122 | return ((NettySSLContextParameters) configuration.getSslContextParameters()).getSslContext();
123 | }
124 |
125 | return null;
126 | }
127 |
128 | protected SslHandler configureClientSSLOnDemand(Channel channel) throws Exception {
129 | if (!producer.getConfiguration().isSsl()) {
130 | return null;
131 | }
132 |
133 | if (producer.getConfiguration().getSslHandler() != null) {
134 | return producer.getConfiguration().getSslHandler();
135 | } else if (sslContext != null) {
136 | URI uri = new URI(producer.getEndpoint().getEndpointUri());
137 | SSLEngine engine = sslContext.newEngine(channel.alloc(), uri.getHost(), uri.getPort());
138 | engine.setUseClientMode(true);
139 | SSLParameters sslParameters = engine.getSSLParameters();
140 | sslParameters.setServerNames(Arrays.asList(new SNIHostName(uri.getHost())));
141 | engine.setSSLParameters(sslParameters);
142 | if (producer.getConfiguration().getSslContextParameters() == null) {
143 | // just set the enabledProtocols if the SslContextParameter doesn't set
144 | engine.setEnabledProtocols(producer.getConfiguration().getEnabledProtocols().split(","));
145 | }
146 | return new SslHandler(engine);
147 | }
148 |
149 | return null;
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/camel/netty/NettySSLContextParameters.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy.camel.netty;
2 |
3 | import io.netty.handler.ssl.SslContext;
4 | import lombok.Data;
5 | import org.apache.camel.support.jsse.SSLContextParameters;
6 |
7 | @Data
8 | public class NettySSLContextParameters extends SSLContextParameters {
9 |
10 | private SslContext sslContext;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/processor/CaptureRequestProcessor.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy.processor;
2 |
3 | import com.amazon.aws.pix.core.audit.AuditLog;
4 | import org.apache.camel.Exchange;
5 | import org.apache.camel.Processor;
6 |
7 | import java.util.Map;
8 | import java.util.stream.Collectors;
9 |
10 | public class CaptureRequestProcessor implements Processor {
11 |
12 | public static final String REQUEST_LOG_PROPERTY = "pix.request.log";
13 |
14 | @Override
15 | public void process(Exchange exchange) throws Exception {
16 | Map headers = exchange.getIn().getHeaders();
17 |
18 | AuditLog auditLog = new AuditLog();
19 | auditLog.setRequestMethod(headers.get("CamelHttpMethod"));
20 | auditLog.setRequestPath(headers.get("CamelHttpPath"));
21 | auditLog.setRequestQuery(headers.get("CamelHttpQuery"));
22 | auditLog.setRequestBody(exchange.getIn().getBody(String.class));
23 | auditLog.setRequestHeader(
24 | headers.entrySet().stream()
25 | .filter(e -> !e.getKey().startsWith("Camel"))
26 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
27 | );
28 |
29 | exchange.setProperty(REQUEST_LOG_PROPERTY, auditLog);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/processor/LogRequestResponseProcessor.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy.processor;
2 |
3 | import com.amazon.aws.pix.core.audit.AuditLog;
4 | import com.amazon.aws.pix.core.util.PixConstants;
5 | import lombok.RequiredArgsConstructor;
6 | import org.apache.camel.Exchange;
7 | import org.apache.camel.Processor;
8 | import software.amazon.awssdk.core.SdkBytes;
9 | import software.amazon.awssdk.services.firehose.FirehoseClient;
10 | import software.amazon.awssdk.services.firehose.model.PutRecordRequest;
11 | import software.amazon.awssdk.services.firehose.model.PutRecordResponse;
12 |
13 | import java.util.Map;
14 | import java.util.stream.Collectors;
15 |
16 | import static com.amazon.aws.pix.cloudhsm.proxy.processor.CaptureRequestProcessor.REQUEST_LOG_PROPERTY;
17 |
18 | @RequiredArgsConstructor
19 | public class LogRequestResponseProcessor implements Processor {
20 |
21 | private final FirehoseClient firehoseClient;
22 | private final String streamName;
23 |
24 | @Override
25 | public void process(Exchange exchange) throws Exception {
26 | Map headers = exchange.getIn().getHeaders();
27 |
28 | AuditLog auditLog = (AuditLog) exchange.getProperty(REQUEST_LOG_PROPERTY);
29 | auditLog.setResponseStatusCode(headers.get("CamelHttpResponseCode"));
30 | auditLog.setResponseSignatureValid(headers.get(PixConstants.PIX_HEADER_SIGNATURE_VALID));
31 | auditLog.setResponseBody(exchange.getIn().getBody(String.class));
32 | auditLog.setResponseHeader(
33 | headers.entrySet().stream()
34 | .filter(e -> !e.getKey().startsWith("Camel"))
35 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
36 | );
37 |
38 | PutRecordRequest putRecordRequest = PutRecordRequest.builder()
39 | .deliveryStreamName(streamName)
40 | .record(builder -> builder.data(SdkBytes.fromUtf8String(auditLog.toJson())))
41 | .build();
42 |
43 | PutRecordResponse putRecordResponse = firehoseClient.putRecord(putRecordRequest);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/processor/SignRequestProcessor.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy.processor;
2 |
3 | import com.amazon.aws.pix.core.xml.XmlSigner;
4 | import lombok.RequiredArgsConstructor;
5 | import org.apache.camel.Exchange;
6 | import org.apache.camel.Processor;
7 |
8 | import java.util.Map;
9 | import java.util.stream.Collectors;
10 |
11 | import static com.amazon.aws.pix.core.util.PixConstants.PIX_HEADERS;
12 | import static com.amazon.aws.pix.core.util.PixConstants.PIX_HEADER_PREFIX;
13 |
14 | @RequiredArgsConstructor
15 | public class SignRequestProcessor implements Processor {
16 |
17 | private final XmlSigner xmlSigner;
18 |
19 | @Override
20 | public final void process(Exchange exchange) throws Exception {
21 | final String body = exchange.getIn().getBody(String.class);
22 | if (body != null && body.length() > 0) {
23 | final String bodySigned = xmlSigner.sign(body);
24 | exchange.getIn().setBody(bodySigned);
25 | }
26 |
27 | exchange.setProperty(
28 | PIX_HEADERS,
29 | exchange.getIn().getHeaders().entrySet().stream()
30 | .filter(e -> e.getKey().startsWith(PIX_HEADER_PREFIX))
31 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/java/com/amazon/aws/pix/cloudhsm/proxy/processor/VerifyResponseProcessor.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.cloudhsm.proxy.processor;
2 |
3 | import com.amazon.aws.pix.core.xml.XmlSigner;
4 | import lombok.RequiredArgsConstructor;
5 | import org.apache.camel.Exchange;
6 | import org.apache.camel.Processor;
7 |
8 | import java.util.Map;
9 |
10 | import static com.amazon.aws.pix.core.util.PixConstants.PIX_HEADERS;
11 | import static com.amazon.aws.pix.core.util.PixConstants.PIX_HEADER_SIGNATURE_VALID;
12 |
13 | @RequiredArgsConstructor
14 | public class VerifyResponseProcessor implements Processor {
15 |
16 | private final XmlSigner xmlSigner;
17 |
18 | @Override
19 | public void process(Exchange exchange) throws Exception {
20 | Map headers = exchange.getIn().getHeaders();
21 | headers.putAll(exchange.getProperty(PIX_HEADERS, Map.class));
22 |
23 | final String body = exchange.getIn().getBody(String.class);
24 | if (body != null && body.length() > 0) {
25 | int statusCode = (int) exchange.getIn().getHeader("CamelHttpResponseCode");
26 | if (200 <= statusCode && statusCode < 300) {
27 | final Boolean valid = xmlSigner.verify(body);
28 | headers.put(PIX_HEADER_SIGNATURE_VALID, valid.toString());
29 | if (!valid) headers.put("CamelHttpResponseCode", 500);
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/proxy/cloudhsm/proxy/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | quarkus.banner.enabled=false
2 |
3 | quarkus.log.level=INFO
4 |
5 | quarkus.camel.main.routes-discovery.enabled=false
6 | camel.context.name=pix-proxy-cloudhsm
7 |
8 | quarkus.package.uber-jar=true
--------------------------------------------------------------------------------
/proxy/core/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 | pix-core
6 | jar
7 |
8 | AWS PIX Core
9 |
10 |
11 | com.amazon.aws
12 | pix
13 | 1.0.0
14 |
15 |
16 |
17 |
18 | org.json
19 | json
20 | 20231013
21 |
22 |
23 |
24 | org.slf4j
25 | slf4j-api
26 |
27 |
28 |
29 | org.projectlombok
30 | lombok
31 |
32 |
33 |
34 | junit
35 | junit
36 | test
37 |
38 |
39 |
40 | commons-io
41 | commons-io
42 | test
43 |
44 |
45 |
46 |
47 |
48 |
49 | maven-compiler-plugin
50 |
51 |
52 | --add-exports
53 | java.xml.crypto/com.sun.org.apache.xml.internal.security.signature=ALL-UNNAMED
54 | --add-exports
55 | java.xml.crypto/org.jcp.xml.dsig.internal.dom=ALL-UNNAMED
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/audit/AuditLog.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.audit;
2 |
3 | import org.json.JSONObject;
4 |
5 | import java.time.Instant;
6 | import java.util.Map;
7 | import java.util.stream.Collectors;
8 |
9 | public class AuditLog {
10 |
11 | private final JSONObject json;
12 |
13 | public AuditLog() {
14 | this.json = new JSONObject();
15 | json.put("request_date", Instant.now().toString());
16 | }
17 |
18 | public void setRequestMethod(Object value) {
19 | put("request_method", value);
20 | }
21 |
22 | public void setRequestPath(Object value) {
23 | put("request_path", value);
24 | }
25 |
26 | public void setRequestQuery(Object value) {
27 | put("request_query", value);
28 | }
29 |
30 | public void setRequestHeader(Map value) {
31 | put("request_header", value);
32 | }
33 |
34 | public void setRequestBody(Object value) {
35 | put("request_body", value);
36 | }
37 |
38 | public void setResponseStatusCode(Object value) {
39 | put("response_status_code", value);
40 | }
41 |
42 | public void setResponseSignatureValid(Object value) {
43 | put("response_signature_valid", value);
44 | }
45 |
46 | public void setResponseHeader(Map value) {
47 | put("response_header", value);
48 | }
49 |
50 | public void setResponseBody(Object value) {
51 | put("response_body", value);
52 | }
53 |
54 | public String toJson() {
55 | return json.toString();
56 | }
57 |
58 | private void put(String key, Object value) {
59 | if (value != null) {
60 | json.put(key, value);
61 | }
62 | }
63 |
64 | private void put(String key, Map value) {
65 | if (value != null) {
66 | json.put(key, "[" + value.entrySet().stream().map(e -> String.format("%s=%s", e.getKey(), e.getValue())).collect(Collectors.joining(", ")) + "]");
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/util/KeyStoreUtil.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.util;
2 |
3 | import lombok.SneakyThrows;
4 |
5 | import java.io.ByteArrayInputStream;
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.nio.charset.StandardCharsets;
9 | import java.security.KeyFactory;
10 | import java.security.KeyStore;
11 | import java.security.PrivateKey;
12 | import java.security.PublicKey;
13 | import java.security.cert.Certificate;
14 | import java.security.cert.CertificateFactory;
15 | import java.security.cert.X509Certificate;
16 | import java.security.spec.PKCS8EncodedKeySpec;
17 | import java.util.Base64;
18 | import java.util.Collection;
19 | import java.util.Iterator;
20 | import java.util.concurrent.atomic.AtomicInteger;
21 |
22 | public abstract class KeyStoreUtil {
23 |
24 | @SneakyThrows
25 | public static KeyStore getKeyStore(File keyStore, String keyStorePassword) {
26 | KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
27 | ks.load(new FileInputStream(keyStore), keyStorePassword.toCharArray());
28 | return ks;
29 | }
30 |
31 | @SneakyThrows
32 | public static KeyStore getKeyStoreFromResource(String keyStore, String keyStorePassword) {
33 | KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
34 | ks.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(keyStore), keyStorePassword.toCharArray());
35 | return ks;
36 | }
37 |
38 | @SneakyThrows
39 | public static KeyStore generateTrustStore(String aliasPrefix, String certificate) {
40 | return generateTrustStore(aliasPrefix, getCertificates(certificate));
41 | }
42 |
43 | @SneakyThrows
44 | public static KeyStore generateTrustStore(String aliasPrefix, File certificate) {
45 | return generateTrustStore(aliasPrefix, getCertificates(certificate));
46 | }
47 |
48 | @SneakyThrows
49 | public static KeyStore generateTrustStore(String aliasPrefix, Collection certificates) {
50 | KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
51 | trustStore.load(null, null);
52 | AtomicInteger index = new AtomicInteger(0);
53 | Iterator certificatesIterator = certificates.iterator();
54 | while (certificatesIterator.hasNext()) {
55 | trustStore.setCertificateEntry(aliasPrefix + "-" + index.getAndIncrement(), certificatesIterator.next());
56 | }
57 | return trustStore;
58 | }
59 |
60 | @SneakyThrows
61 | public static KeyStore generateKeyStore(String alias, String privateKey, String certificate) {
62 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
63 | keyStore.load(null, null);
64 |
65 | Collection certificates = getCertificates(certificate);
66 | keyStore.setKeyEntry(alias, getPrivateKey(privateKey), null, certificates.toArray(new Certificate[certificates.size()]));
67 | return keyStore;
68 | }
69 |
70 | @SneakyThrows
71 | public static Collection getCertificates(String certificate) {
72 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
73 | ByteArrayInputStream inputStream = new ByteArrayInputStream(certificate.getBytes(StandardCharsets.UTF_8));
74 | return (Collection) certificateFactory.generateCertificates(inputStream);
75 | }
76 |
77 | @SneakyThrows
78 | public static Collection getCertificates(File certificate) {
79 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
80 | return (Collection) certificateFactory.generateCertificates(new FileInputStream(certificate));
81 | }
82 |
83 | @SneakyThrows
84 | public static X509Certificate getCertificate(String certificate) {
85 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
86 | ByteArrayInputStream inputStream = new ByteArrayInputStream(certificate.getBytes(StandardCharsets.UTF_8));
87 | return (X509Certificate) certificateFactory.generateCertificate(inputStream);
88 | }
89 |
90 | @SneakyThrows
91 | public static X509Certificate getCertificate(File certificate) {
92 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
93 | return (X509Certificate) certificateFactory.generateCertificate(new FileInputStream(certificate));
94 | }
95 |
96 | @SneakyThrows
97 | public static X509Certificate getCertificateFromResource(String certificate) {
98 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
99 | return (X509Certificate) certificateFactory.generateCertificate(Thread.currentThread().getContextClassLoader().getResourceAsStream(certificate));
100 | }
101 |
102 | @SneakyThrows
103 | public static PrivateKey getPrivateKey(String key) {
104 | key = key.replace("-----BEGIN PRIVATE KEY-----", "")
105 | .replaceAll(System.lineSeparator(), "")
106 | .replace("-----END PRIVATE KEY-----", "");
107 |
108 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key));
109 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
110 | return keyFactory.generatePrivate(keySpec);
111 | }
112 |
113 | @SneakyThrows
114 | public static PublicKey getPublicKey(String key) {
115 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key.getBytes(StandardCharsets.UTF_8));
116 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
117 | return keyFactory.generatePublic(keySpec);
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/util/PixConstants.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.util;
2 |
3 | public interface PixConstants {
4 |
5 | String PIX_HEADERS = "pix-headers";
6 | String PIX_HEADER_PREFIX = "pix-";
7 | String PIX_HEADER_SIGNATURE_VALID = "pix-signature-valid";
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/xml/Iso20022URIDereferencer.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.xml;
2 |
3 | import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput;
4 | import lombok.AllArgsConstructor;
5 | import lombok.NonNull;
6 | import org.jcp.xml.dsig.internal.dom.ApacheNodeSetData;
7 | import org.w3c.dom.Document;
8 | import org.w3c.dom.Node;
9 | import org.w3c.dom.NodeList;
10 |
11 | import javax.xml.crypto.*;
12 |
13 | @AllArgsConstructor
14 | public class Iso20022URIDereferencer implements URIDereferencer {
15 |
16 | public static final String DOCUMENT = "Document";
17 | public static final String APP_HDR = "AppHdr";
18 | public static final String APP_HDR_URI = "";
19 |
20 | @NonNull
21 | private final Document document;
22 |
23 | @NonNull
24 | private final URIDereferencer defaultDereferencer;
25 |
26 | @Override
27 | public Data dereference(@NonNull URIReference uriRef, @NonNull XMLCryptoContext context) throws URIReferenceException {
28 | if (uriRef.getURI() == null) {
29 | return getData(context, DOCUMENT);
30 | } else if (uriRef.getURI().equals(APP_HDR_URI)) {
31 | return getData(context, APP_HDR);
32 | } else {
33 | return defaultDereferencer.dereference(uriRef, context);
34 | }
35 | }
36 |
37 | private Data getData(XMLCryptoContext context, String element) throws URIReferenceException {
38 | Node node = getNode(element);
39 | XMLSignatureInput result = new XMLSignatureInput(node);
40 | result.setSecureValidation(secureValidation(context));
41 | result.setExcludeComments(true);
42 | result.setMIMEType("text/xml");
43 | return new ApacheNodeSetData(result);
44 | }
45 |
46 | private Node getNode(String tagName) throws URIReferenceException {
47 | NodeList nodeList = document.getElementsByTagName(tagName);
48 | if (nodeList.getLength() == 0) {
49 | throw new URIReferenceException("No <" + tagName + "> Element detected");
50 | } else if (nodeList.getLength() > 1) {
51 | throw new URIReferenceException("Multiple <" + tagName + "> Elements detected");
52 | }
53 | return nodeList.item(0);
54 | }
55 |
56 | private boolean secureValidation(XMLCryptoContext xmlCryptoContext) {
57 | return xmlCryptoContext == null ? false : getBoolean(xmlCryptoContext, "org.jcp.xml.dsig.secureValidation");
58 | }
59 |
60 | private boolean getBoolean(XMLCryptoContext xmlCryptoContext, String name) {
61 | Boolean value = (Boolean) xmlCryptoContext.getProperty(name);
62 | return value != null && value;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/xml/Iso20022XmlSigner.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.xml;
2 |
3 | import lombok.NonNull;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.w3c.dom.Document;
6 | import org.w3c.dom.Element;
7 | import org.w3c.dom.Node;
8 | import org.w3c.dom.NodeList;
9 |
10 | import javax.xml.crypto.dsig.Reference;
11 | import javax.xml.crypto.dsig.Transform;
12 | import javax.xml.crypto.dsig.XMLSignatureFactory;
13 | import javax.xml.crypto.dsig.dom.DOMSignContext;
14 | import javax.xml.crypto.dsig.dom.DOMValidateContext;
15 | import javax.xml.crypto.dsig.keyinfo.KeyInfo;
16 | import javax.xml.crypto.dsig.spec.TransformParameterSpec;
17 | import java.security.InvalidAlgorithmParameterException;
18 | import java.security.KeyStore;
19 | import java.security.NoSuchAlgorithmException;
20 | import java.security.PrivateKey;
21 | import java.security.cert.X509Certificate;
22 | import java.util.List;
23 |
24 | @Slf4j
25 | public class Iso20022XmlSigner extends XmlSigner {
26 |
27 | public static final String FORMAT = "ISO-20022";
28 | public static final String SGNTR = "Sgntr";
29 | public static final String APP_HDR = "AppHdr";
30 | public static final String APP_HDR_URI = "";
31 | public static final String DOCUMENT_URI = null;
32 | public static final String ID_PREFIX_URI = "#";
33 |
34 | public Iso20022XmlSigner(@NonNull PrivateKey privateKey, @NonNull X509Certificate certificate, @NonNull KeyStore trustStore) {
35 | super(privateKey, certificate, trustStore);
36 | }
37 |
38 | @Override
39 | protected List getReferences(XMLSignatureFactory signatureFactory, KeyInfo keyInfo) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
40 | return List.of(
41 | signatureFactory.newReference(
42 | ID_PREFIX_URI + keyInfo.getId(),
43 | signatureFactory.newDigestMethod(xmlDigestMethod, null),
44 | List.of(
45 | signatureFactory.newTransform(canonicalizationMethod, (TransformParameterSpec) null)
46 | ),
47 | null,
48 | null
49 | ),
50 | signatureFactory.newReference(
51 | APP_HDR_URI,
52 | signatureFactory.newDigestMethod(xmlDigestMethod, null),
53 | List.of(
54 | signatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null),
55 | signatureFactory.newTransform(canonicalizationMethod, (TransformParameterSpec) null)
56 | ),
57 | null,
58 | null
59 | ),
60 | signatureFactory.newReference(
61 | DOCUMENT_URI,
62 | signatureFactory.newDigestMethod(xmlDigestMethod, null),
63 | List.of(
64 | signatureFactory.newTransform(canonicalizationMethod, (TransformParameterSpec) null)
65 | ),
66 | null,
67 | null
68 | )
69 | );
70 | }
71 |
72 | @Override
73 | protected Element getSignatureEnvelop(Document document) {
74 | NodeList nodeList = document.getElementsByTagName(SGNTR);
75 | for (int i = 0; i < nodeList.getLength(); i++) {
76 | Node node = nodeList.item(i);
77 | if (node.getParentNode().getNodeName().equals(APP_HDR)) {
78 | node.getParentNode().removeChild(node);
79 | }
80 | }
81 |
82 | Node appHdr = getNodeByTagName(document, APP_HDR);
83 | if (appHdr == null) throw new IllegalStateException("No <" + APP_HDR + "> Element found");
84 |
85 | Element sgntr = document.createElement(SGNTR);
86 | appHdr.appendChild(sgntr);
87 |
88 | return sgntr;
89 | }
90 |
91 | @Override
92 | protected DOMSignContext getSignContext(XMLSignatureFactory signatureFactory, Element signatureEnvelop) {
93 | DOMSignContext domSignContext = super.getSignContext(signatureFactory, signatureEnvelop);
94 | domSignContext.setURIDereferencer(new Iso20022URIDereferencer(signatureEnvelop.getOwnerDocument(), signatureFactory.getURIDereferencer()));
95 | return domSignContext;
96 | }
97 |
98 | @Override
99 | protected DOMValidateContext getValidateContext(XMLSignatureFactory signatureFactory, Node signatureNode) {
100 | DOMValidateContext validateContext = super.getValidateContext(signatureFactory, signatureNode);
101 | validateContext.setURIDereferencer(new Iso20022URIDereferencer(signatureNode.getOwnerDocument(), signatureFactory.getURIDereferencer()));
102 | return validateContext;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/xml/X509IssuerSerialKeySelector.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.xml;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.NonNull;
5 | import lombok.SneakyThrows;
6 |
7 | import javax.security.auth.x500.X500Principal;
8 | import javax.xml.crypto.*;
9 | import javax.xml.crypto.dsig.keyinfo.KeyInfo;
10 | import javax.xml.crypto.dsig.keyinfo.X509Data;
11 | import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
12 | import java.security.KeyStore;
13 | import java.security.KeyStoreException;
14 | import java.security.PublicKey;
15 | import java.security.cert.Certificate;
16 | import java.security.cert.X509CertSelector;
17 | import java.security.cert.X509Certificate;
18 | import java.util.Enumeration;
19 |
20 | @AllArgsConstructor
21 | public class X509IssuerSerialKeySelector extends KeySelector {
22 |
23 | @NonNull
24 | private final KeyStore keyStore;
25 |
26 | @Override
27 | public KeySelectorResult select(@NonNull KeyInfo keyInfo, Purpose purpose, AlgorithmMethod algorithmMethod, XMLCryptoContext xmlCryptoContext) throws KeySelectorException {
28 | try {
29 | X509Data x509Data = (X509Data) keyInfo.getContent().get(0);
30 | X509IssuerSerial x509IssuerSerial = (X509IssuerSerial) x509Data.getContent().get(0);
31 | return () -> getPublicKey(x509IssuerSerial);
32 | } catch (Exception e) {
33 | throw new KeySelectorException("Failed to find Certificate", e);
34 | }
35 | }
36 |
37 | @SneakyThrows
38 | private PublicKey getPublicKey(X509IssuerSerial x509IssuerSerial) {
39 | X509CertSelector x509CertSelector = new X509CertSelector();
40 | x509CertSelector.setSerialNumber(x509IssuerSerial.getSerialNumber());
41 | x509CertSelector.setIssuer(new X500Principal(x509IssuerSerial.getIssuerName()).getName());
42 |
43 | Enumeration aliases = keyStore.aliases();
44 | while (aliases.hasMoreElements()) {
45 | Certificate certificate = keyStore.getCertificate(aliases.nextElement());
46 | if (certificate != null && x509CertSelector.match(certificate)) {
47 | ((X509Certificate) certificate).checkValidity();
48 | return certificate.getPublicKey();
49 | }
50 | }
51 |
52 | throw new KeyStoreException("Certificate is not present in KeyStore");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/proxy/core/src/main/java/com/amazon/aws/pix/core/xml/XmlSigner.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.xml;
2 |
3 | import lombok.NonNull;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.w3c.dom.Document;
6 | import org.w3c.dom.Element;
7 | import org.w3c.dom.Node;
8 | import org.w3c.dom.NodeList;
9 | import org.xml.sax.SAXException;
10 |
11 | import javax.xml.XMLConstants;
12 | import javax.xml.crypto.KeySelector;
13 | import javax.xml.crypto.dsig.*;
14 | import javax.xml.crypto.dsig.dom.DOMSignContext;
15 | import javax.xml.crypto.dsig.dom.DOMValidateContext;
16 | import javax.xml.crypto.dsig.keyinfo.KeyInfo;
17 | import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
18 | import javax.xml.crypto.dsig.keyinfo.X509Data;
19 | import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
20 | import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
21 | import javax.xml.crypto.dsig.spec.TransformParameterSpec;
22 | import javax.xml.parsers.DocumentBuilderFactory;
23 | import javax.xml.parsers.ParserConfigurationException;
24 | import javax.xml.transform.Transformer;
25 | import javax.xml.transform.TransformerException;
26 | import javax.xml.transform.TransformerFactory;
27 | import javax.xml.transform.dom.DOMSource;
28 | import javax.xml.transform.stream.StreamResult;
29 | import java.io.ByteArrayInputStream;
30 | import java.io.ByteArrayOutputStream;
31 | import java.io.IOException;
32 | import java.io.InputStream;
33 | import java.nio.charset.StandardCharsets;
34 | import java.security.InvalidAlgorithmParameterException;
35 | import java.security.KeyStore;
36 | import java.security.NoSuchAlgorithmException;
37 | import java.security.PrivateKey;
38 | import java.security.cert.X509Certificate;
39 | import java.util.Collections;
40 | import java.util.Iterator;
41 | import java.util.List;
42 | import java.util.UUID;
43 |
44 | @Slf4j
45 | public class XmlSigner {
46 |
47 | protected final PrivateKey privateKey;
48 | protected final X509Certificate certificate;
49 |
50 | protected final KeySelector keySelector;
51 |
52 | protected final String xmlDigestMethod;
53 | protected final String xmlSignatureMethod;
54 | protected final String canonicalizationMethod;
55 |
56 | public XmlSigner(@NonNull PrivateKey privateKey, @NonNull X509Certificate certificate,
57 | @NonNull KeyStore trustStore) {
58 | this.privateKey = privateKey;
59 | this.certificate = certificate;
60 |
61 | this.keySelector = new X509IssuerSerialKeySelector(trustStore);
62 |
63 | this.xmlDigestMethod = DigestMethod.SHA256;
64 | this.xmlSignatureMethod = SignatureMethod.RSA_SHA256;
65 | this.canonicalizationMethod = CanonicalizationMethod.EXCLUSIVE;
66 | }
67 |
68 | public String sign(@NonNull String xml) {
69 | ByteArrayOutputStream xmlSigned = sign(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
70 | return new String(xmlSigned.toByteArray(), StandardCharsets.UTF_8);
71 | }
72 |
73 | public byte[] sign(@NonNull byte[] xml) {
74 | ByteArrayOutputStream xmlSigned = sign(new ByteArrayInputStream(xml));
75 | return xmlSigned.toByteArray();
76 | }
77 |
78 | public ByteArrayOutputStream sign(@NonNull InputStream xml) {
79 | try {
80 | Document document = getDocument(xml);
81 | XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance();
82 | KeyInfo keyInfo = getKeyInfo(signatureFactory);
83 | List references = getReferences(signatureFactory, keyInfo);
84 | SignedInfo signedInfo = getSignedInfo(signatureFactory, references);
85 | XMLSignature signature = signatureFactory.newXMLSignature(signedInfo, keyInfo);
86 | Element signatureEnvelop = getSignatureEnvelop(document);
87 |
88 | signature.sign(getSignContext(signatureFactory, signatureEnvelop));
89 | return transform(document);
90 | } catch (Exception e) {
91 | throw new RuntimeException(e);
92 | }
93 | }
94 |
95 | public boolean verify(@NonNull String xml) {
96 | return verify(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
97 | }
98 |
99 | public boolean verify(@NonNull byte[] xml) {
100 | return verify(new ByteArrayInputStream(xml));
101 | }
102 |
103 | public boolean verify(@NonNull InputStream xml) {
104 | try {
105 | Document document = getDocument(xml);
106 | Node signatureNode = getNodeByTagNameNS(document, XMLSignature.XMLNS, "Signature");
107 | if (signatureNode == null) {
108 | log.error("No Signature found!");
109 | return false;
110 | }
111 |
112 | XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance();
113 |
114 | DOMValidateContext validateContext = getValidateContext(signatureFactory, signatureNode);
115 | XMLSignature signature = signatureFactory.unmarshalXMLSignature(validateContext);
116 |
117 | boolean valid = signature.validate(validateContext);
118 |
119 | if (!valid) {
120 | StringBuilder error = new StringBuilder();
121 | error.append("Signature failed core validation!").append(System.lineSeparator());
122 | boolean validStatus = signature.getSignatureValue().validate(validateContext);
123 | error.append("signature validation status: ").append(validStatus).append(System.lineSeparator());
124 | if (!validStatus) {
125 | Iterator referenceIterator = signature.getSignedInfo().getReferences().iterator();
126 | for (int i = 0; referenceIterator.hasNext(); i++) {
127 | error.append("ref[").append(i).append("] validity status: ")
128 | .append(referenceIterator.next().validate(validateContext))
129 | .append(System.lineSeparator());
130 | }
131 | log.error(error.toString());
132 | }
133 | }
134 |
135 | return valid;
136 | } catch (Exception e) {
137 | log.error("failed to verify signature", e);
138 | return false;
139 | }
140 | }
141 |
142 | protected DOMSignContext getSignContext(XMLSignatureFactory signatureFactory, Element signatureEnvelop) {
143 | DOMSignContext domSignContext = new DOMSignContext(privateKey, signatureEnvelop);
144 | domSignContext.putNamespacePrefix(XMLSignature.XMLNS, "ds");
145 | return domSignContext;
146 | }
147 |
148 | protected Document getDocument(InputStream xml) throws SAXException, IOException, ParserConfigurationException {
149 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
150 | dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
151 | dbf.setXIncludeAware(false); // Default false for java 8. Disable XML Inclusions leading to SSRF -
152 | // https://portswigger.net/web-security/xxe/lab-xinclude-attack
153 | dbf.setExpandEntityReferences(false); // Default true for java 8. Disable expand entity reference nodes leading
154 | // to Billion laughs attack [CWE-776].
155 | dbf.setNamespaceAware(true);
156 | return dbf.newDocumentBuilder().parse(xml);
157 | }
158 |
159 | protected KeyInfo getKeyInfo(XMLSignatureFactory signatureFactory) {
160 | KeyInfoFactory keyInfoFactory = signatureFactory.getKeyInfoFactory();
161 | X509IssuerSerial x509IssuerSerial = keyInfoFactory
162 | .newX509IssuerSerial(certificate.getSubjectX500Principal().getName(), certificate.getSerialNumber());
163 | X509Data x509Data = keyInfoFactory.newX509Data(Collections.singletonList(x509IssuerSerial));
164 | return keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data), UUID.randomUUID().toString());
165 | }
166 |
167 | protected List getReferences(XMLSignatureFactory signatureFactory, KeyInfo keyInfo)
168 | throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
169 | return List.of(
170 | signatureFactory.newReference(
171 | "#" + keyInfo.getId(),
172 | signatureFactory.newDigestMethod(xmlDigestMethod, null),
173 | List.of(
174 | signatureFactory.newTransform(canonicalizationMethod, (TransformParameterSpec) null)),
175 | null,
176 | null),
177 | signatureFactory.newReference(
178 | "", // in this case we are signing the whole document, so the URI of ""
179 | signatureFactory.newDigestMethod(xmlDigestMethod, null),
180 | List.of(
181 | signatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null),
182 | signatureFactory.newTransform(canonicalizationMethod, (TransformParameterSpec) null)),
183 | null,
184 | null));
185 | }
186 |
187 | protected SignedInfo getSignedInfo(XMLSignatureFactory signatureFactory, List references)
188 | throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
189 | return signatureFactory.newSignedInfo(
190 | signatureFactory.newCanonicalizationMethod(canonicalizationMethod, (C14NMethodParameterSpec) null),
191 | signatureFactory.newSignatureMethod(xmlSignatureMethod, null),
192 | references);
193 | }
194 |
195 | protected Element getSignatureEnvelop(Document document) {
196 | return document.getDocumentElement();
197 | }
198 |
199 | protected ByteArrayOutputStream transform(Document document) throws TransformerException {
200 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
201 | TransformerFactory tf = TransformerFactory.newInstance();
202 | tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
203 | tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
204 | Transformer trans = tf.newTransformer();
205 | trans.transform(new DOMSource(document), new StreamResult(outputStream));
206 | return outputStream;
207 | }
208 |
209 | protected Node getNodeByTagNameNS(Document document, String ns, String tagName) {
210 | NodeList nodeList = document.getElementsByTagNameNS(ns, tagName);
211 | return nodeList.getLength() > 0 ? nodeList.item(0) : null;
212 | }
213 |
214 | protected Node getNodeByTagName(Document document, String tagName) {
215 | NodeList nodeList = document.getElementsByTagName(tagName);
216 | return nodeList.getLength() > 0 ? nodeList.item(0) : null;
217 | }
218 |
219 | protected DOMValidateContext getValidateContext(XMLSignatureFactory signatureFactory, Node signatureNode) {
220 | return new DOMValidateContext(keySelector, signatureNode);
221 | }
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/proxy/core/src/test/java/com/amazon/aws/pix/core/test/xml/Iso20022XmlSignerTest.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.test.xml;
2 |
3 | import com.amazon.aws.pix.core.util.KeyStoreUtil;
4 | import com.amazon.aws.pix.core.xml.Iso20022XmlSigner;
5 | import com.amazon.aws.pix.core.xml.XmlSigner;
6 | import lombok.SneakyThrows;
7 | import org.apache.commons.io.FileUtils;
8 | import org.junit.Assert;
9 | import org.junit.Test;
10 |
11 | import java.io.File;
12 | import java.security.KeyStore;
13 | import java.security.PublicKey;
14 | import java.security.cert.X509Certificate;
15 |
16 | public class Iso20022XmlSignerTest {
17 |
18 | private final KeyStore keyStore;
19 |
20 | private final XmlSigner xmlSigner;
21 |
22 | private final PublicKey publicKey;
23 |
24 | @SneakyThrows
25 | public Iso20022XmlSignerTest() {
26 | keyStore = KeyStoreUtil.getKeyStoreFromResource("security/client.jks", "secret");
27 | KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("client", new KeyStore.PasswordProtection("secret".toCharArray()));
28 |
29 | publicKey = privateKeyEntry.getCertificate().getPublicKey();
30 |
31 | xmlSigner = new Iso20022XmlSigner(privateKeyEntry.getPrivateKey(), (X509Certificate) privateKeyEntry.getCertificate(), keyStore);
32 | }
33 |
34 | @Test
35 | @SneakyThrows
36 | public void test() {
37 | String xml = FileUtils.readFileToString(new File(this.getClass().getClassLoader().getResource("xml/pacs.008_CONTA_1_msg.xml").getFile()), "UTF-8");
38 |
39 | System.out.println();
40 | System.out.println("XML: ");
41 | System.out.println("----------------------------");
42 | System.out.println(xml);
43 | System.out.println("----------------------------");
44 | System.out.println();
45 |
46 | System.out.println();
47 | System.out.println("XML Signed: ");
48 | System.out.println("----------------------------");
49 | String xmlSigned = xmlSigner.sign(xml);
50 | System.out.println(xmlSigned);
51 | System.out.println("----------------------------");
52 | System.out.println();
53 |
54 | System.out.println();
55 | System.out.println("XML Signature Validation: ");
56 | System.out.println("----------------------------");
57 |
58 | long keyStoreTimeStart = System.currentTimeMillis();
59 | boolean valid = xmlSigner.verify(xmlSigned);
60 | long keyStoreTime = System.currentTimeMillis() - keyStoreTimeStart;
61 | System.out.println("KeyStore Time: " + keyStoreTime);
62 | Assert.assertTrue(valid);
63 |
64 | System.out.println(valid);
65 | System.out.println("----------------------------");
66 | System.out.println();
67 |
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/proxy/core/src/test/java/com/amazon/aws/pix/core/test/xml/XmlSignerTest.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.core.test.xml;
2 |
3 | import com.amazon.aws.pix.core.util.KeyStoreUtil;
4 | import com.amazon.aws.pix.core.xml.XmlSigner;
5 | import lombok.SneakyThrows;
6 | import org.apache.commons.io.FileUtils;
7 | import org.junit.Assert;
8 | import org.junit.Test;
9 |
10 | import java.io.File;
11 | import java.security.KeyStore;
12 | import java.security.cert.X509Certificate;
13 |
14 | public class XmlSignerTest {
15 |
16 | private final KeyStore keyStore;
17 | private final XmlSigner xmlSigner;
18 |
19 | @SneakyThrows
20 | public XmlSignerTest() {
21 | keyStore = KeyStoreUtil.getKeyStoreFromResource("security/client.jks", "secret");
22 | KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("client", new KeyStore.PasswordProtection("secret".toCharArray()));
23 |
24 | xmlSigner = new XmlSigner(privateKeyEntry.getPrivateKey(), (X509Certificate) privateKeyEntry.getCertificate(), keyStore);
25 | }
26 |
27 | @Test
28 | @SneakyThrows
29 | public void test() {
30 | String xml = FileUtils.readFileToString(new File(this.getClass().getClassLoader().getResource("xml/test.xml").getFile()), "UTF-8");
31 |
32 | System.out.println();
33 | System.out.println("XML: ");
34 | System.out.println("----------------------------");
35 | System.out.println(xml);
36 | System.out.println("----------------------------");
37 | System.out.println();
38 |
39 | System.out.println();
40 | System.out.println("XML Signed: ");
41 | System.out.println("----------------------------");
42 | String xmlSigned = xmlSigner.sign(xml);
43 | System.out.println(xmlSigned);
44 | System.out.println("----------------------------");
45 | System.out.println();
46 |
47 | System.out.println();
48 | System.out.println("XML Signature Validation: ");
49 | System.out.println("----------------------------");
50 |
51 | boolean valid = xmlSigner.verify(xmlSigned);
52 | Assert.assertTrue(valid);
53 |
54 | System.out.println(valid);
55 | System.out.println("----------------------------");
56 | System.out.println();
57 |
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/proxy/core/src/test/resources/security/client.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/proxy/core/src/test/resources/security/client.jks
--------------------------------------------------------------------------------
/proxy/core/src/test/resources/xml/pacs.008_CONTA_1_msg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 00038166
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 99999010
18 |
19 |
20 |
21 |
22 | M0003816612345678901234567890123
23 | pacs.008.spi.1.4
24 | 2020-01-01T08:30:12.000Z
25 |
26 |
27 |
28 |
29 |
30 | M0003816612345678901234567890123
31 | 2020-01-01T08:30:12.000Z
32 | 1
33 |
34 | CLRG
35 |
36 |
37 | HIGH
38 |
39 |
40 |
41 |
42 | E9999901012341234123412345678900
43 | 90000
44 |
45 | 1000.00
46 | 2020-01-01T08:30:00.000Z
47 | SLEV
48 |
49 | Fulano da Silva
50 |
51 |
52 |
53 | 70000000000
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 500000
62 | 3000
63 |
64 |
65 |
66 | CACC
67 |
68 |
69 |
70 |
71 |
72 | 10000000
73 |
74 |
75 |
76 |
77 |
78 |
79 | 20000000
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 80000000000
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | 600000
96 | 4000
97 |
98 |
99 |
100 | SVGS
101 |
102 |
103 |
104 | Campo livre [0]
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/proxy/core/src/test/resources/xml/test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | AWS
4 | PIX
5 |
--------------------------------------------------------------------------------
/proxy/kms/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | pix-kms-proxy-sync
5 | jar
6 |
7 | Pix KMS Proxy Sync
8 |
9 |
10 | com.amazon.aws
11 | pix
12 | 1.0.0
13 |
14 |
15 |
16 | 3.1.0
17 |
18 |
19 |
20 |
21 |
22 | com.amazon.aws
23 | pix-core
24 |
25 |
26 |
27 |
28 | software.amazon.awssdk
29 | kms-jce-provider
30 | 1.0.0
31 |
32 |
33 | software.amazon.awssdk
34 | netty-nio-client
35 |
36 |
37 | software.amazon.awssdk
38 | apache-client
39 |
40 |
41 |
42 |
43 |
44 | software.amazon.awssdk
45 | secretsmanager
46 |
47 |
48 | software.amazon.awssdk
49 | netty-nio-client
50 |
51 |
52 | software.amazon.awssdk
53 | apache-client
54 |
55 |
56 |
57 |
58 |
59 | software.amazon.awssdk
60 | ssm
61 |
62 |
63 | software.amazon.awssdk
64 | netty-nio-client
65 |
66 |
67 | software.amazon.awssdk
68 | apache-client
69 |
70 |
71 |
72 |
73 |
74 | software.amazon.awssdk
75 | firehose
76 |
77 |
78 | software.amazon.awssdk
79 | netty-nio-client
80 |
81 |
82 | software.amazon.awssdk
83 | apache-client
84 |
85 |
86 |
87 |
88 |
89 | software.amazon.awssdk
90 | url-connection-client
91 |
92 |
93 |
94 | org.projectlombok
95 | lombok
96 |
97 |
98 |
99 | io.quarkus
100 | quarkus-amazon-lambda
101 |
102 |
103 |
104 | io.quarkus
105 | quarkus-test-amazon-lambda
106 | test
107 |
108 |
109 |
110 | io.quarkus
111 | quarkus-junit5
112 | test
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | io.quarkus
121 | quarkus-maven-plugin
122 |
123 |
124 |
125 |
126 |
127 |
128 | native
129 |
130 |
131 | native
132 |
133 |
134 |
135 |
136 |
137 | io.quarkus
138 | quarkus-maven-plugin
139 | ${quarkus.version}
140 |
141 |
142 |
143 | native-image
144 |
145 |
146 | true
147 |
148 | -H:ReflectionConfigurationFiles=reflection-config.json
149 | -H:ResourceConfigurationFiles=resources-config.json
150 | -J-Djdk.internal.httpclient.disableHostnameVerification
151 |
152 |
153 |
154 |
155 |
156 |
157 | org.apache.maven.plugins
158 | maven-assembly-plugin
159 | ${assembly-plugin.version}
160 |
161 |
162 | zip-assembly
163 | package
164 |
165 | single
166 |
167 |
168 | function
169 |
170 | src/assembly/zip.xml
171 |
172 | false
173 | false
174 |
175 |
176 |
177 |
178 |
179 | org.apache.maven.plugins
180 | maven-antrun-plugin
181 | 3.0.0
182 |
183 |
184 | copy-runner-to-bootstrap
185 | package
186 |
187 | run
188 |
189 |
190 |
191 |
192 |
193 |
194 | true
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/proxy/kms/src/assembly/zip.xml:
--------------------------------------------------------------------------------
1 |
4 | lambda-package
5 |
6 | zip
7 |
8 | false
9 |
10 |
11 | ${project.build.directory}${file.separator}${artifactId}-${version}-runner
12 | /
13 | bootstrap
14 | 755
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/proxy/kms/src/main/java/com/amazon/aws/pix/kms/proxy/config/Config.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.kms.proxy.config;
2 |
3 | import lombok.Getter;
4 | import org.eclipse.microprofile.config.inject.ConfigProperty;
5 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
6 | import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
7 | import software.amazon.awssdk.regions.Region;
8 | import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
9 | import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
10 | import software.amazon.awssdk.services.ssm.SsmClient;
11 | import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest;
12 | import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse;
13 | import software.amazon.awssdk.services.ssm.model.Parameter;
14 |
15 | import javax.annotation.PostConstruct;
16 | import javax.inject.Singleton;
17 | import java.util.HashMap;
18 | import java.util.Map;
19 | import java.util.Optional;
20 | import java.util.stream.Collectors;
21 |
22 | @Singleton
23 | public class Config {
24 |
25 | @ConfigProperty(name = "aws.region")
26 | String regionId;
27 |
28 | @Getter
29 | Region region;
30 |
31 | @ConfigProperty(name = "pix.spi.proxy")
32 | Boolean spi;
33 |
34 | private enum Secret {
35 | MtlsPrivateKey;
36 |
37 | public static final String PATH = "/pix/proxy/kms/";
38 |
39 | public String getSecretId() {
40 | return String.format("%s%s", PATH, this.name());
41 | }
42 | }
43 |
44 | private enum Param {
45 | MtlsCertificate,
46 | SignatureKeyId,
47 | SignatureCertificate,
48 | BcbMtlsCertificate,
49 | BcbSignatureCertificate,
50 | BcbDictEndpoint,
51 | BcbSpiEndpoint,
52 | SpiAuditStream,
53 | DictAuditStream;
54 |
55 | public static final String PATH = "/pix/proxy/kms/";
56 |
57 | public String getParamName() {
58 | return String.format("%s%s", PATH, this.name());
59 | }
60 | }
61 |
62 | private Map secrets;
63 | private Map parameters;
64 |
65 | @PostConstruct
66 | void init() {
67 | region = Region.of(regionId);
68 | loadSecrets();
69 | loadParameters();
70 | }
71 |
72 | public String getMtlsPrivateKey() {
73 | return secrets.get(Secret.MtlsPrivateKey);
74 | }
75 |
76 | public String getMtlsCertificate() {
77 | return getParameter(Param.MtlsCertificate);
78 | }
79 |
80 | public String getSignatureKeyId() {
81 | return getParameter(Param.SignatureKeyId);
82 | }
83 |
84 | public String getSignatureCertificate() {
85 | return getParameter(Param.SignatureCertificate);
86 | }
87 |
88 | public String getAuditStream() {
89 | return spi ? getParameter(Param.SpiAuditStream) : getParameter(Param.DictAuditStream);
90 | }
91 |
92 | public String getBcbMtlsCertificate() {
93 | return getParameter(Param.BcbMtlsCertificate);
94 | }
95 |
96 | public String getBcbSignatureCertificate() {
97 | return getParameter(Param.BcbSignatureCertificate);
98 | }
99 |
100 | public String getBcbEndpoint() {
101 | return spi ? getParameter(Param.BcbSpiEndpoint) : getParameter(Param.BcbDictEndpoint);
102 | }
103 |
104 | public boolean isIso20022() {
105 | return spi;
106 | }
107 |
108 | private void loadSecrets() {
109 | SecretsManagerClient secretsManagerClient = SecretsManagerClient.builder()
110 | .region(region)
111 | .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
112 | .httpClientBuilder(UrlConnectionHttpClient.builder())
113 | .build();
114 |
115 | secrets = new HashMap<>();
116 |
117 | GetSecretValueResponse secretValue = secretsManagerClient.getSecretValue(builder -> builder.secretId(Secret.MtlsPrivateKey.getSecretId()));
118 | secrets.put(Secret.MtlsPrivateKey, secretValue.secretString());
119 | }
120 |
121 | private void loadParameters() {
122 | SsmClient ssmClient = SsmClient.builder()
123 | .region(region)
124 | .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
125 | .httpClientBuilder(UrlConnectionHttpClient.builder())
126 | .build();
127 |
128 | parameters = new HashMap<>();
129 | String nextToken = null;
130 | do {
131 | GetParametersByPathResponse response = ssmClient.getParametersByPath(GetParametersByPathRequest.builder().nextToken(nextToken).path(Param.PATH).recursive(true).build());
132 | parameters.putAll(response.parameters().stream().collect(Collectors.toMap(Parameter::name, Parameter::value)));
133 | nextToken = response.nextToken();
134 | } while (nextToken != null);
135 | }
136 |
137 | private String getParameter(Param param) {
138 | return Optional.ofNullable(parameters.get(param.getParamName()))
139 | .orElseThrow(() -> new IllegalStateException(String.format("Parameter %s not found!", param.getParamName())));
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/proxy/kms/src/main/java/com/amazon/aws/pix/kms/proxy/service/Logger.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.kms.proxy.service;
2 |
3 | import com.amazon.aws.pix.core.audit.AuditLog;
4 | import com.amazon.aws.pix.core.util.PixConstants;
5 | import com.amazon.aws.pix.kms.proxy.config.Config;
6 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
7 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
8 | import io.quarkus.runtime.Startup;
9 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
10 | import software.amazon.awssdk.core.SdkBytes;
11 | import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
12 | import software.amazon.awssdk.services.firehose.FirehoseClient;
13 | import software.amazon.awssdk.services.firehose.model.PutRecordRequest;
14 |
15 | import java.util.List;
16 | import java.util.Map;
17 | import java.util.stream.Collectors;
18 |
19 | @Startup
20 | public class Logger {
21 |
22 | private final FirehoseClient firehoseClient;
23 | private final String streamName;
24 |
25 | public Logger(Config config) {
26 | firehoseClient = FirehoseClient.builder()
27 | .region(config.getRegion())
28 | .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
29 | .httpClientBuilder(UrlConnectionHttpClient.builder())
30 | .build();
31 |
32 | streamName = config.getAuditStream();
33 | }
34 |
35 | public void log(APIGatewayProxyRequestEvent request, APIGatewayProxyResponseEvent response) {
36 |
37 | AuditLog auditLog = new AuditLog();
38 |
39 | auditLog.setRequestMethod(request.getHttpMethod());
40 | auditLog.setRequestPath(request.getPath());
41 | auditLog.setRequestBody(request.getBody());
42 | auditLog.setRequestHeader(flatList(request.getMultiValueHeaders()));
43 |
44 | auditLog.setResponseStatusCode(response.getStatusCode());
45 | auditLog.setResponseSignatureValid(isSignatureValid(response));
46 | auditLog.setResponseBody(response.getBody());
47 | auditLog.setResponseHeader(response.getHeaders());
48 |
49 | PutRecordRequest putRecordRequest = PutRecordRequest.builder()
50 | .deliveryStreamName(streamName)
51 | .record(builder -> builder.data(SdkBytes.fromUtf8String(auditLog.toJson())))
52 | .build();
53 |
54 | firehoseClient.putRecord(putRecordRequest);
55 | }
56 |
57 | private Map flatList(Map> map) {
58 | if (map == null) return null;
59 | return map.entrySet().stream().collect(Collectors.toMap(
60 | e -> e.getKey(),
61 | e -> String.join(", ", e.getValue())
62 | ));
63 | }
64 |
65 | private String isSignatureValid(APIGatewayProxyResponseEvent response) {
66 | if (response.getHeaders() == null) return null;
67 | return response.getHeaders().get(PixConstants.PIX_HEADER_SIGNATURE_VALID);
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/proxy/kms/src/main/java/com/amazon/aws/pix/kms/proxy/service/Sender.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.kms.proxy.service;
2 |
3 | import com.amazon.aws.pix.core.util.KeyStoreUtil;
4 | import com.amazon.aws.pix.kms.proxy.config.Config;
5 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
6 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
7 | import io.quarkus.runtime.Startup;
8 | import lombok.SneakyThrows;
9 |
10 | import javax.net.ssl.KeyManagerFactory;
11 | import javax.net.ssl.SSLContext;
12 | import javax.net.ssl.TrustManagerFactory;
13 | import java.net.URI;
14 | import java.net.http.HttpClient;
15 | import java.net.http.HttpRequest;
16 | import java.net.http.HttpResponse;
17 | import java.security.KeyStore;
18 | import java.util.Map;
19 | import java.util.Set;
20 | import java.util.stream.Collectors;
21 |
22 | import static com.amazon.aws.pix.core.util.PixConstants.PIX_HEADER_PREFIX;
23 |
24 | @Startup
25 | public class Sender {
26 |
27 | private static final Set RESTRICTED_HEADERS = Set.of("connection", "content-length", "date", "expect", "from", "host", "upgrade", "via", "warning");
28 |
29 | private final HttpClient httpClient;
30 | private final String endpoint;
31 |
32 | public Sender(Config config) {
33 | httpClient = createHttpClient(config);
34 | endpoint = config.getBcbEndpoint();
35 | }
36 |
37 | @SneakyThrows
38 | private HttpClient createHttpClient(Config config) {
39 | KeyStore keyStore = KeyStoreUtil.generateKeyStore("pix", config.getMtlsPrivateKey(), config.getMtlsCertificate());
40 | KeyStore trustStore = KeyStoreUtil.generateTrustStore("bcb", config.getBcbMtlsCertificate());
41 |
42 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
43 | trustManagerFactory.init(trustStore);
44 |
45 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
46 | keyManagerFactory.init(keyStore, null);
47 |
48 | SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
49 | sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
50 |
51 | return HttpClient.newBuilder().sslContext(sslContext).build();
52 | }
53 |
54 | public APIGatewayProxyResponseEvent send(APIGatewayProxyRequestEvent request) {
55 | try {
56 | HttpRequest.Builder httpRequestBuilder = HttpRequest.newBuilder().uri(new URI(String.format("https://%s/%s", endpoint, request.getPath())));
57 | setHeaders(request, httpRequestBuilder);
58 | setMethodAndBody(request, httpRequestBuilder);
59 |
60 | HttpResponse httpResponse = httpClient.send(httpRequestBuilder.build(), HttpResponse.BodyHandlers.ofString());
61 | return getResponse(httpResponse);
62 |
63 | } catch (Exception e) {
64 | e.printStackTrace();
65 |
66 | APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
67 | response.setStatusCode(500);
68 | response.setBody(e.getMessage());
69 | return response;
70 | }
71 | }
72 |
73 | private void setHeaders(APIGatewayProxyRequestEvent request, HttpRequest.Builder httpRequestBuilder) {
74 | if (request.getHeaders() != null) {
75 | request.getMultiValueHeaders().forEach((k, l) -> {
76 | if (!RESTRICTED_HEADERS.contains(k.toLowerCase())) {
77 | l.forEach(v -> httpRequestBuilder.header(k, v));
78 | }
79 | });
80 | }
81 | }
82 |
83 | private void setMethodAndBody(APIGatewayProxyRequestEvent request, HttpRequest.Builder httpRequestBuilder) {
84 | HttpRequest.BodyPublisher bodyPublisher;
85 | if (isNotBlank(request.getBody())) {
86 | bodyPublisher = HttpRequest.BodyPublishers.ofString(request.getBody());
87 | } else {
88 | bodyPublisher = HttpRequest.BodyPublishers.noBody();
89 | }
90 | httpRequestBuilder.method(request.getHttpMethod(), bodyPublisher);
91 | }
92 |
93 | private APIGatewayProxyResponseEvent getResponse(HttpResponse httpResponse) {
94 | APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
95 |
96 | response.setStatusCode(httpResponse.statusCode());
97 | response.setBody(httpResponse.body());
98 | response.setHeaders(
99 | httpResponse.headers().map().entrySet().stream()
100 | .collect(Collectors.toMap(e -> e.getKey(), e -> String.join(", ", e.getValue())))
101 | );
102 |
103 | Map pixRequestHeaders = httpResponse.request().headers().map().entrySet().stream()
104 | .filter(e -> e.getKey().startsWith(PIX_HEADER_PREFIX))
105 | .collect(Collectors.toMap(e -> e.getKey(), e -> String.join(", ", e.getValue())));
106 |
107 | response.getHeaders().putAll(pixRequestHeaders);
108 |
109 | return response;
110 | }
111 |
112 | private boolean isNotBlank(String value) {
113 | return value != null && !value.trim().isEmpty();
114 | }
115 |
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/proxy/kms/src/main/java/com/amazon/aws/pix/kms/proxy/service/Signer.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.kms.proxy.service;
2 |
3 | import com.amazon.aws.pix.core.util.KeyStoreUtil;
4 | import com.amazon.aws.pix.core.xml.Iso20022XmlSigner;
5 | import com.amazon.aws.pix.core.xml.XmlSigner;
6 | import com.amazon.aws.pix.kms.proxy.config.Config;
7 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
8 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
9 | import io.quarkus.runtime.Startup;
10 | import lombok.extern.slf4j.Slf4j;
11 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
12 | import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
13 | import software.amazon.awssdk.services.kms.KmsClient;
14 | import software.amazon.awssdk.services.kms.jce.provider.KmsProvider;
15 | import software.amazon.awssdk.services.kms.jce.provider.rsa.KmsRSAKeyFactory;
16 |
17 | import java.security.KeyStore;
18 | import java.security.PrivateKey;
19 | import java.security.Security;
20 | import java.security.cert.X509Certificate;
21 |
22 | import static com.amazon.aws.pix.core.util.PixConstants.PIX_HEADER_SIGNATURE_VALID;
23 |
24 | @Slf4j
25 | @Startup
26 | public class Signer {
27 |
28 | private final XmlSigner xmlSigner;
29 |
30 | public Signer(Config config) {
31 | KmsClient kmsClient = KmsClient.builder()
32 | .region(config.getRegion())
33 | .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
34 | .httpClientBuilder(UrlConnectionHttpClient.builder())
35 | .build();
36 |
37 | Security.addProvider(new KmsProvider(kmsClient));
38 |
39 | PrivateKey privateKey = KmsRSAKeyFactory.getPrivateKey(config.getSignatureKeyId());
40 | X509Certificate certificate = KeyStoreUtil.getCertificate(config.getSignatureCertificate());
41 | KeyStore trustStore = KeyStoreUtil.generateTrustStore("bcb", config.getBcbSignatureCertificate());
42 |
43 | xmlSigner = config.isIso20022() ? new Iso20022XmlSigner(privateKey, certificate, trustStore) : new XmlSigner(privateKey, certificate, trustStore);
44 | }
45 |
46 | public void sign(APIGatewayProxyRequestEvent request) {
47 | if (isNotBlank(request.getBody())) {
48 | request.setBody(xmlSigner.sign(request.getBody()));
49 | }
50 | }
51 |
52 | public void verify(APIGatewayProxyResponseEvent response) {
53 | if (isSuccessfulResponse(response.getStatusCode()) && isNotBlank(response.getBody())) {
54 | if (xmlSigner.verify(response.getBody())) {
55 | response.getHeaders().put(PIX_HEADER_SIGNATURE_VALID, "true");
56 | } else {
57 | response.setStatusCode(500);
58 | response.getHeaders().put(PIX_HEADER_SIGNATURE_VALID, "false");
59 | }
60 | }
61 | }
62 |
63 | private boolean isNotBlank(String value) {
64 | return value != null && !value.trim().isEmpty();
65 | }
66 |
67 | private boolean isSuccessfulResponse(int statusCode) {
68 | return 200 <= statusCode && statusCode < 300;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/proxy/kms/src/main/java/com/amazon/aws/pix/kms/proxy/sync/ProxyHandler.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.kms.proxy.sync;
2 |
3 | import com.amazon.aws.pix.kms.proxy.service.Logger;
4 | import com.amazon.aws.pix.kms.proxy.service.Sender;
5 | import com.amazon.aws.pix.kms.proxy.service.Signer;
6 | import com.amazonaws.services.lambda.runtime.Context;
7 | import com.amazonaws.services.lambda.runtime.RequestHandler;
8 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
9 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
10 | import lombok.AllArgsConstructor;
11 |
12 | @AllArgsConstructor
13 | public class ProxyHandler implements RequestHandler {
14 |
15 | private final Signer signer;
16 | private final Sender sender;
17 | private final Logger logger;
18 |
19 | @Override
20 | public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request, Context context) {
21 | signer.sign(request);
22 | APIGatewayProxyResponseEvent response = sender.send(request);
23 | signer.verify(response);
24 | logger.log(request, response);
25 | return response;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/proxy/kms/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | quarkus.banner.enabled=false
2 | quarkus.log.level=WARN
3 | quarkus.ssl.native=true
--------------------------------------------------------------------------------
/proxy/kms/src/main/resources/reflection-config.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name" : "com.sun.org.apache.xml.internal.security.transforms.implementations.TransformC14NExclusive",
4 | "methods":[{"name":"","parameterTypes":[] }]
5 | },
6 | {
7 | "name" : "com.sun.org.apache.xml.internal.security.transforms.implementations.TransformEnvelopedSignature",
8 | "methods":[{"name":"","parameterTypes":[] }]
9 | }
10 | ]
--------------------------------------------------------------------------------
/proxy/kms/src/main/resources/resources-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "resources": [],
3 | "bundles": [
4 | {"name":"com.sun.org.apache.xerces.internal.impl.msg.XMLMessages"}
5 | ]
6 | }
--------------------------------------------------------------------------------
/proxy/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.amazon.aws
5 | pix
6 | 1.0.0
7 | pom
8 |
9 | AWS PIX (Brazilian Instant Payment System)
10 |
11 |
12 | UTF-8
13 | 11
14 | ${java.version}
15 | ${java.version}
16 |
17 | 1.7.0.Final
18 | 1.0.0
19 |
20 | 2.13.0
21 | 1.18.12
22 |
23 | 4.13.1
24 | 2.14.0
25 |
26 |
27 |
28 | core
29 | cloudhsm
30 | kms
31 | test
32 |
33 |
34 |
35 |
36 |
37 |
38 | com.amazon.aws
39 | pix-core
40 | 1.0.0
41 |
42 |
43 |
44 | io.quarkus
45 | quarkus-bom
46 | ${quarkus.version}
47 | pom
48 | import
49 |
50 |
51 |
52 | org.apache.camel.quarkus
53 | camel-quarkus-bom
54 | ${camel-quarkus.version}
55 | pom
56 | import
57 |
58 |
59 |
60 | software.amazon.awssdk
61 | bom
62 | ${aws.bom.version}
63 | pom
64 | import
65 |
66 |
67 |
68 | org.projectlombok
69 | lombok
70 | ${lombok.version}
71 | provided
72 |
73 |
74 |
75 | junit
76 | junit
77 | ${junit.version}
78 | test
79 |
80 |
81 |
82 | commons-io
83 | commons-io
84 | ${commons-io.version}
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | org.apache.maven.plugins
95 | maven-compiler-plugin
96 | 3.6.1
97 |
98 |
99 | org.apache.maven.plugins
100 | maven-shade-plugin
101 | 3.1.1
102 |
103 |
104 |
105 |
106 | package
107 |
108 | shade
109 |
110 |
111 |
112 |
113 |
114 | io.quarkus
115 | quarkus-maven-plugin
116 | ${quarkus.version}
117 |
118 |
119 |
120 | build
121 |
122 |
123 |
124 |
125 |
126 | org.jboss.jandex
127 | jandex-maven-plugin
128 | 1.0.7
129 |
130 |
131 | make-index
132 |
133 | jandex
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/proxy/test/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 | pix-proxy-test
6 | jar
7 |
8 | PIX Proxy Test
9 |
10 |
11 | com.amazon.aws
12 | pix
13 | 1.0.0
14 |
15 |
16 |
17 |
18 | com.amazon.aws
19 | pix-core
20 |
21 |
22 |
23 | org.projectlombok
24 | lombok
25 |
26 |
27 |
28 | org.apache.camel.quarkus
29 | camel-quarkus-netty-http
30 |
31 |
32 |
33 | io.netty
34 | netty-transport-native-epoll
35 | linux-x86_64
36 |
37 |
38 |
39 | commons-io
40 | commons-io
41 |
42 |
43 |
44 | software.amazon.awssdk
45 | ssm
46 |
47 |
48 | software.amazon.awssdk
49 | netty-nio-client
50 |
51 |
52 | software.amazon.awssdk
53 | apache-client
54 |
55 |
56 |
57 |
58 |
59 | software.amazon.awssdk
60 | url-connection-client
61 |
62 |
63 |
64 |
65 |
66 |
67 | io.quarkus
68 | quarkus-maven-plugin
69 |
70 | 5006
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM amazoncorretto:11
2 | WORKDIR /work/
3 | COPY proxy/test/src/main/docker/ssl /work/ssl
4 | COPY proxy/test/target/*-runner.jar /work/application.jar
5 |
6 | ENV WORK_SSL_DIR=/work/ssl
7 |
8 | # set up permissions for user `1001`
9 | RUN chmod 775 /work \
10 | && chown -R 1001 /work \
11 | && chmod -R "g+rwX" /work \
12 | && chown -R 1001:root /work
13 |
14 | EXPOSE 8181
15 | EXPOSE 9191
16 | USER 1001
17 |
18 | CMD ["java", "-jar", "application.jar"]
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/dict-response.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OWNERSHIP
5 | +5561988887777
6 | PHONE
7 |
8 | 12345678
9 | 00001
10 | 0007654321
11 | CACC
12 |
13 |
14 | NATURAL_PERSON
15 | 11122233300
16 | João Silva
17 |
18 | 87654321
19 | 123e4567-e89b-12d3-a456-426655440000
20 | OPEN
21 | 2020-01-17T10:00:00Z
22 | 2020-01-17T10:00:00Z
23 | 2020-01-10T10:00:00Z
24 |
25 |
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/mtls.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDnDCCAoSgAwIBAgIEBR5HdTANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJC
3 | UjELMAkGA1UECBMCREYxETAPBgNVBAcTCEJyYXNpbGlhMQwwCgYDVQQKEwNCQ0Ix
4 | DDAKBgNVBAsTA1BJWDEZMBcGA1UEAwwQKi5waS5yc2ZuLm5ldC5icjAeFw0yMDA3
5 | MDUxMzUxMTFaFw0zMDA3MDMxMzUxMTFaMGQxCzAJBgNVBAYTAkJSMQswCQYDVQQI
6 | EwJERjERMA8GA1UEBxMIQnJhc2lsaWExDDAKBgNVBAoTA0JDQjEMMAoGA1UECxMD
7 | UElYMRkwFwYDVQQDDBAqLnBpLnJzZm4ubmV0LmJyMIIBIjANBgkqhkiG9w0BAQEF
8 | AAOCAQ8AMIIBCgKCAQEAztOPl4NGjpvf/d07FHHkbJKC7xRwoBhvTpTQ/vQp9E3v
9 | hUI4fIgvvsXAzaknifMysdtk1BRS3Urk6tiL9ZCKEVcqfTPTdawcBi2AABrBvWYx
10 | jDTk5dK1o8wPcUyWRDMRXiWv7grODR75u5a+s3bZTOWLIDxGpY2cuDSRWK0bT8Zh
11 | of+8cn4yML03A83mqrfri1rahH/WpGzwOPk6+pv2m/VKv6GTS1ADD5xTExO5Zotg
12 | wYAuU/zUVZ007CvHGGVoJ87hbUr8EmW1DsgxcPGWeKZ0SzCZkV88eLaD6sedyg0q
13 | 5w1ACRPSK0fRXHpxLkqckJ72hyinr/S/axOvM9bijQIDAQABo1YwVDAdBgNVHQ4E
14 | FgQU+hfJS2Fp0POVlnuOxCsgquNqJocwMwYDVR0RAQH/BCkwJ4IJbG9jYWxob3N0
15 | ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
16 | hjIm6Cj35JR1cbKsIlUFlFVfN7/D9Rx2GOq7JtD4SbzTbDyJ3zm/usVMFFNDNuSs
17 | mJhqLpTKmPX9Akp55RSdnLDEs2tDs7rN5Fy5BODwHblnnyflN0oSihnGop0TtEtv
18 | Gw+zWXms4Pm9Vyi3l+UQA3ENJICP3H7iiPyxj0kThjhFnoIn4kqd+/xSp/BBR6JB
19 | 1UofthomxU4qcYcb4gWBYdgaGzoUIk3W3iMBzQDQmmiqAgKYhEA24SrOgkwWw0/o
20 | 3RNL7mYI5L3tivyrc0/K3/aE0yPhDAMHpC8V7taUwnb4k7rClB0bqF5GmCnWzBis
21 | NAoejbjou87yzYUTY8nRnw==
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/mtls.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/proxy/test/src/main/docker/ssl/mtls.jks
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/mtls.key:
--------------------------------------------------------------------------------
1 | Bag Attributes
2 | friendlyName: mtls
3 | localKeyID: 54 69 6D 65 20 31 35 39 33 39 35 37 32 34 34 33 35 39
4 | Key Attributes:
5 | -----BEGIN PRIVATE KEY-----
6 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDO04+Xg0aOm9/9
7 | 3TsUceRskoLvFHCgGG9OlND+9Cn0Te+FQjh8iC++xcDNqSeJ8zKx22TUFFLdSuTq
8 | 2Iv1kIoRVyp9M9N1rBwGLYAAGsG9ZjGMNOTl0rWjzA9xTJZEMxFeJa/uCs4NHvm7
9 | lr6zdtlM5YsgPEaljZy4NJFYrRtPxmGh/7xyfjIwvTcDzeaqt+uLWtqEf9akbPA4
10 | +Tr6m/ab9Uq/oZNLUAMPnFMTE7lmi2DBgC5T/NRVnTTsK8cYZWgnzuFtSvwSZbUO
11 | yDFw8ZZ4pnRLMJmRXzx4toPqx53KDSrnDUAJE9IrR9FcenEuSpyQnvaHKKev9L9r
12 | E68z1uKNAgMBAAECggEAGtJe1cRFeysFwlNVXRJkEBxz9HNPI8Pnc2ZjMB0T78XH
13 | oR10W9oBbxkQJtuf8Ajk8yHDkqlMEbyrsTUB+YeUECmgrfnxm1oFjFuMNPdDVr+Q
14 | /2DAqnlH6wUEn6nR9ug0gWQby6AFFriEpGopMvbfuVQvVa/bbY7O2yieDMTuPMr+
15 | n2Nmo6v/jbyeNCpOap4WSaiOkvnVNNI/DrRjvFwM4xxY86A3vsHP3/1uqKlBp3bR
16 | /k697jc/jq6gX9+iWmQKSAfOaHc6kLkeId56CC1RzikL5Aywb0AhKweFEU9Jn0t4
17 | LMzSCaeIYjaF44Dp9VCvdaiYECg44hqWVuOjLz3XQQKBgQDqQfVholPWfB3mHGUX
18 | v5mRBWf/jHrTXU6fjnqjNJnagXZZbnvY8T10BIfEZPynBsPWcOYx95l5Rr/FQ2H7
19 | xzE+atpy6J5somXpoiK9c8wNpFGkLoyC23dw2lVu705YNx34T+DLUK7GUc49vuun
20 | 8dJNBippAJNdi80kQuoqKyQoEQKBgQDiBdOnYQd4mYnH+sMQX8NFHV9f/4HrfhNx
21 | 69gR1QPZcMGCM2Ht1GhyAy3mXCm8xzOKhZe4Mqo1Izir6rFzD7Vc2gTlESuUlvSY
22 | Cb12cGjqhzIFe9YqaWFxOF4/ZelLZmHijOcIDxD6Fe5Sw4QzGvSwGVk9xbVlyFx8
23 | TGYPoA5uvQKBgQDRLQSHogNqynmIOGKsyhG+RS2QK0ih4/eI0hkAEsAvNNsREG7W
24 | UNOm/USeQe641nmYykdC5uzMNjiXVIvx3vRB3/ggE0cjif+ml3id4wCaTdf42vft
25 | ATuuMceHv3gdnVnNmMYJxonJ3NJkoE6dMHmw/YhxKmpsZaFQd8/Qx6UJgQKBgAP3
26 | UjV3sGffWdmWfuHErGqED25hz2fQeIrhNpNph33DSsH8INXJimOlxqJqL5lObYsa
27 | uQTUbsQr8AykO6TnO6l7ceaLHJao53XbheWUJ2MmKCyMdb+cxcb01EKMiUF3dHRD
28 | QLKVgRjCVcJCCAnViUGl0DkTRRGaKHVN6eSQTHCJAoGBAN2f02veYZY6zYDNad0r
29 | 8zDaOhJ2KmOkBiNMSSXujjposMphYwGxQYVxW+yn98xbW+SyfayrIEGzTszZa0AJ
30 | 8TNMdHqi7Uz/Yrp01M5hSEFR3V5l+oBsGExXGJrZuBdYUwcRlwoFQ4qp3X66Vj44
31 | LbcRssnfMbx5Lwjc4rnY9627
32 | -----END PRIVATE KEY-----
33 |
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/mtls.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/proxy/test/src/main/docker/ssl/mtls.p12
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/sig.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDnDCCAoSgAwIBAgIEaLkRBjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJC
3 | UjELMAkGA1UECBMCREYxETAPBgNVBAcTCEJyYXNpbGlhMQwwCgYDVQQKEwNCQ0Ix
4 | DDAKBgNVBAsTA1BJWDEZMBcGA1UEAwwQKi5waS5yc2ZuLm5ldC5icjAeFw0yMDA3
5 | MDUxMzUwMzVaFw0zMDA3MDMxMzUwMzVaMGQxCzAJBgNVBAYTAkJSMQswCQYDVQQI
6 | EwJERjERMA8GA1UEBxMIQnJhc2lsaWExDDAKBgNVBAoTA0JDQjEMMAoGA1UECxMD
7 | UElYMRkwFwYDVQQDDBAqLnBpLnJzZm4ubmV0LmJyMIIBIjANBgkqhkiG9w0BAQEF
8 | AAOCAQ8AMIIBCgKCAQEAy38YHSwphFKHH49rFbl/caqP/ugD0vD3n6lrGzC9xukG
9 | q81bVYXKBzbVtn8gxCOsUCIktMoZNe6QCUeTGshreohIFKdzV/ZH70eZcCOcGoZX
10 | 3evPJuRYIpjjxp0CJbj71EubylavUNpgGjj9v02ezlto94oQN87YR77sDBPBPGeW
11 | CwaYPN8KY0tW8CqrmJXkMsA+pd/1tv3QbBpkUbEgbTvrVTz+9qEUpAg6SeytIulg
12 | icLQrklYPv/Jex4KKcZxAp6SGBrMYmuCViw40qd1SriWk5HYfmMzXSy6DJ7HO5Im
13 | 0F1g43XdEWr0hUmUpsFu2JTIO5qGgx9OcQ6Tw74mgQIDAQABo1YwVDAdBgNVHQ4E
14 | FgQUMswECZ5M0yc1aMkxWdNucYsKjU8wMwYDVR0RAQH/BCkwJ4IJbG9jYWxob3N0
15 | ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEA
16 | varWSOwcE2A5sIsJbPHczsDXiVOObfJjVol/JBXPH00A8uZ6hbsWDCNp7XZHjheW
17 | snw9acXzKvi+NY/kCYaSegsUr9O+2BBcGhCN4LI5uITE9s3YZKyl+2rqk93P7EDB
18 | RSitjPXeRm9ANPZCR90h+amZQLNbfiK0Povrv61isFqLfdGXnk9B6tfLB+baeS8f
19 | HhxEM22sd+5yo9rUZOdAGI72SMzgaMT1AJZbVbb3z2ymDByJkgTAsVdkkSNkqwEi
20 | Y3lg6cJ5thj5NdaXWc8wCzG6L85uAVV/7eh0SMJ2ITMJwkrrqtrX47LeNPrtCTy/
21 | B8Um+Ao1f9w4nbxP53d+6w==
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/sig.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/proxy/test/src/main/docker/ssl/sig.jks
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/sig.key:
--------------------------------------------------------------------------------
1 | Bag Attributes
2 | friendlyName: sig
3 | localKeyID: 54 69 6D 65 20 31 35 39 33 39 35 37 32 32 31 33 31 38
4 | Key Attributes:
5 | -----BEGIN PRIVATE KEY-----
6 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLfxgdLCmEUocf
7 | j2sVuX9xqo/+6APS8PefqWsbML3G6QarzVtVhcoHNtW2fyDEI6xQIiS0yhk17pAJ
8 | R5MayGt6iEgUp3NX9kfvR5lwI5wahlfd688m5FgimOPGnQIluPvUS5vKVq9Q2mAa
9 | OP2/TZ7OW2j3ihA3zthHvuwME8E8Z5YLBpg83wpjS1bwKquYleQywD6l3/W2/dBs
10 | GmRRsSBtO+tVPP72oRSkCDpJ7K0i6WCJwtCuSVg+/8l7HgopxnECnpIYGsxia4JW
11 | LDjSp3VKuJaTkdh+YzNdLLoMnsc7kibQXWDjdd0RavSFSZSmwW7YlMg7moaDH05x
12 | DpPDviaBAgMBAAECggEBAJB6geJkcm2SMoGDz+Gyu/s1v9qZW+2uTAE9xd+eMX9B
13 | /wyHVqtXu+J3WjCh/d+D/FlAhJMfUdrqr7UG7jh7x2NN0u9nHAt9vgDy8sudXL2w
14 | WD4lqAPS54hYyI9A9j9WIOXPcKKt/PcItGapw+7I61FTsSjfCzi0UPZPZUQF2pJJ
15 | LS99jYVifNuPYcGXeDvc6tDH8Le3AR7hnMlZyM7MVA30opIuL5M+6f4v8LagVsBx
16 | DQZP0X0NyI5I8MFd26j2XeEp0IbnnIFmuiYvdre+vYsYPQdBvtoMjiSUk8YIT3A6
17 | 0whn2TE97F6SaOxqsQDralnxbHZ/hA2wIodlthiLamECgYEA5hx6ioeWaOB8/RWx
18 | 82mqs2CkGK5WfJ6lj7WSHgwmFNHU2eL/H0s6AT5vY3Pgv9X5Wh/YwVM295rHQX98
19 | dxFXTR6iZ/u6vqqzeDQpVCeJmCw2JBo+6Xa4FxYECq3fN8OWq/tG8oHrqVTlblMU
20 | xgBUzr1UuULwD25n6Ux9G5p8W40CgYEA4mQSsx7zpfpqGbgAWflCMV9ht1aOGdkd
21 | HCdRDqq/nbkWMDrgzl/7HLMsU8S7Z9gL/9CmmQG0QSBr/VL/LVFxpPdMURqUssRS
22 | gudocORwCv6LjrkbniSqkfpRAvCDYmUN+yOjSok+80Nma25BXZb2PbnvZkOGGyZX
23 | 3DR3mkKUP8UCgYEAiATffpFlHDtORn+Tf7G/QGbouNocr29LqCYI6BAAVqAt9FmU
24 | upfKAsA4MY5mlJ7T1S0sACLDRBtOmu3T0cIDW8e28kY/fcMDcRNMNr1kPPotSqvk
25 | 8jXF3Yea/gEHCDxqDODscB7SAXb4Y31isyOw/b+2nAfwRP7bF3GqJi3sQmUCgYBN
26 | Iv/kHDW0ZgBkxvcHsRBGtlMbHtjIIdvzTcomjzM+0GOpB0a1yqpK+xmtaSjZlxlv
27 | jm5VT+lVD3D8HZ1omYl5RfGw6LeluZLLm4FQheVPOa/fb17joWak2n9j2wNJMVl0
28 | Ko5mxSC3zk1FaYnIE7nPCl1xoKw+7++bzhy6aNkOaQKBgCaJIyjaYpmlYsncLLwK
29 | 5EWtZ1WrXI43he2tiwrzeDSjQKEstnUOZ8d272l6SRNntEelnlCbKixhq7KG41/U
30 | yy2p0KbD5CsAFbg74GeKqvlu7xoBEjfBaA50i/in+DBUKYs751c11SuW8JwCYvzD
31 | But0HH4NKxY3OxFOf6yBSwVq
32 | -----END PRIVATE KEY-----
33 |
--------------------------------------------------------------------------------
/proxy/test/src/main/docker/ssl/sig.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/pix-proxy-samples/fa20042d19d4c0b78f6898f3053b4cd0729938d0/proxy/test/src/main/docker/ssl/sig.p12
--------------------------------------------------------------------------------
/proxy/test/src/main/java/com/amazon/aws/pix/proxy/test/PixProxyTestRouteBuilder.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws.pix.proxy.test;
2 |
3 | import com.amazon.aws.pix.core.util.KeyStoreUtil;
4 | import com.amazon.aws.pix.core.xml.Iso20022XmlSigner;
5 | import com.amazon.aws.pix.core.xml.XmlSigner;
6 | import lombok.RequiredArgsConstructor;
7 | import lombok.SneakyThrows;
8 | import org.apache.camel.Exchange;
9 | import org.apache.camel.Processor;
10 | import org.apache.camel.builder.EndpointConsumerBuilder;
11 | import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
12 | import org.apache.camel.support.jsse.KeyManagersParameters;
13 | import org.apache.camel.support.jsse.KeyStoreParameters;
14 | import org.apache.camel.support.jsse.SSLContextParameters;
15 | import org.apache.camel.support.jsse.TrustManagersParameters;
16 | import org.apache.commons.io.FileUtils;
17 | import org.eclipse.microprofile.config.inject.ConfigProperty;
18 | import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
19 | import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
20 | import software.amazon.awssdk.regions.Region;
21 | import software.amazon.awssdk.services.ssm.SsmClient;
22 | import software.amazon.awssdk.services.ssm.model.GetParametersByPathRequest;
23 | import software.amazon.awssdk.services.ssm.model.GetParametersByPathResponse;
24 | import software.amazon.awssdk.services.ssm.model.Parameter;
25 |
26 | import javax.annotation.PostConstruct;
27 | import javax.enterprise.context.ApplicationScoped;
28 | import javax.net.ssl.TrustManagerFactory;
29 | import java.io.File;
30 | import java.security.KeyStore;
31 | import java.security.KeyStoreException;
32 | import java.security.NoSuchAlgorithmException;
33 | import java.security.UnrecoverableEntryException;
34 | import java.security.cert.X509Certificate;
35 | import java.util.*;
36 | import java.util.stream.Collectors;
37 |
38 | @ApplicationScoped
39 | public class PixProxyTestRouteBuilder extends EndpointRouteBuilder {
40 |
41 | @ConfigProperty(name = "aws.default.region")
42 | String awsDefaultRegion;
43 |
44 | @ConfigProperty(name = "work.ssl.dir")
45 | String workSslDir;
46 |
47 | enum CloudHsmParam {
48 | MtlsCertificate,
49 | SignatureCertificate;
50 |
51 | public String getParamName() {
52 | return String.format("/pix/proxy/cloudhsm/%s", this.name());
53 | }
54 | }
55 |
56 | enum KmsParam {
57 | MtlsCertificate,
58 | SignatureCertificate,
59 | SignatureSelfSignedCertificate;
60 |
61 | public String getParamName() {
62 | return String.format("/pix/proxy/kms/%s", this.name());
63 | }
64 | }
65 |
66 | private Map parameters;
67 | private XmlSigner xmlSigner;
68 | private Iso20022XmlSigner iso20022XmlSigner;
69 |
70 | @PostConstruct
71 | void init() throws Exception {
72 | loadParameters();
73 | createXmlSigners();
74 | }
75 |
76 | @Override
77 | public void configure() throws Exception {
78 | createSslContext();
79 |
80 | from(bcbEndpoint("0.0.0.0:8181"))
81 | .transform(body().convertToString())
82 | .process(new DictProcessor(xmlSigner, workSslDir));
83 |
84 | from(bcbEndpoint("0.0.0.0:9191"))
85 | .transform(body().convertToString())
86 | .process(new SpiProcessor(iso20022XmlSigner));
87 |
88 | from(checkEndpoint()).transform(constant("OK"));
89 | }
90 |
91 | private EndpointConsumerBuilder bcbEndpoint(String endpoint) {
92 | return nettyHttp(endpoint)
93 | .matchOnUriPrefix(true)
94 | .ssl(true)
95 | .enabledProtocols("TLSv1.2")
96 | .needClientAuth(true)
97 | .sslContextParameters("#sslContextParameters")
98 | .advanced().nativeTransport(true);
99 | }
100 |
101 | private EndpointConsumerBuilder checkEndpoint() {
102 | return netty("tcp://0.0.0.0:7171")
103 | .advanced().nativeTransport(true);
104 | }
105 |
106 | private void loadParameters() {
107 | SsmClient ssmClient = SsmClient.builder()
108 | .region(Region.of(awsDefaultRegion))
109 | .httpClientBuilder(UrlConnectionHttpClient.builder())
110 | .build();
111 |
112 | parameters = new HashMap<>();
113 | String nextToken = null;
114 | do {
115 | GetParametersByPathResponse response = ssmClient.getParametersByPath(GetParametersByPathRequest.builder().nextToken(nextToken).path("/pix/proxy/").recursive(true).build());
116 | parameters.putAll(response.parameters().stream().collect(Collectors.toMap(Parameter::name, Parameter::value)));
117 | nextToken = response.nextToken();
118 | } while (nextToken != null);
119 | }
120 |
121 | private void createSslContext() throws KeyStoreException, NoSuchAlgorithmException {
122 | KeyStoreParameters ksp = new KeyStoreParameters();
123 | ksp.setResource(workSslDir + "/mtls.jks");
124 | ksp.setPassword("secret");
125 |
126 | KeyManagersParameters kmp = new KeyManagersParameters();
127 | kmp.setKeyPassword("secret");
128 | kmp.setKeyStore(ksp);
129 |
130 | List trustCertificates = new ArrayList<>();
131 | Optional.ofNullable(parameters.get(CloudHsmParam.MtlsCertificate.getParamName()))
132 | .ifPresent(cert -> trustCertificates.addAll(KeyStoreUtil.getCertificates(cert)));
133 | Optional.ofNullable(parameters.get(KmsParam.MtlsCertificate.getParamName()))
134 | .ifPresent(cert -> trustCertificates.addAll(KeyStoreUtil.getCertificates(cert)));
135 |
136 | if (trustCertificates.isEmpty()) throw new IllegalStateException("mTLS (CloudHSM/KMS) Certificates not found!");
137 |
138 | KeyStore trustStore = KeyStoreUtil.generateTrustStore("psp", trustCertificates);
139 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
140 | trustManagerFactory.init(trustStore);
141 |
142 | TrustManagersParameters tmp = new TrustManagersParameters();
143 | tmp.setTrustManager(trustManagerFactory.getTrustManagers()[0]);
144 |
145 | SSLContextParameters sslContextParameters = new SSLContextParameters();
146 | sslContextParameters.setKeyManagers(kmp);
147 | sslContextParameters.setTrustManagers(tmp);
148 |
149 | getContext().getRegistry().bind("sslContextParameters", sslContextParameters);
150 | }
151 |
152 | private void createXmlSigners() throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException {
153 |
154 | List trustCertificates = new ArrayList<>();
155 | Optional.ofNullable(parameters.get(CloudHsmParam.SignatureCertificate.getParamName()))
156 | .ifPresent(cert -> trustCertificates.addAll(KeyStoreUtil.getCertificates(cert)));
157 | Optional.ofNullable(parameters.get(KmsParam.SignatureCertificate.getParamName()))
158 | .ifPresent(cert -> trustCertificates.addAll(KeyStoreUtil.getCertificates(cert)));
159 | Optional.ofNullable(parameters.get(KmsParam.SignatureSelfSignedCertificate.getParamName()))
160 | .ifPresent(cert -> trustCertificates.addAll(KeyStoreUtil.getCertificates(cert)));
161 |
162 | if (trustCertificates.isEmpty())
163 | throw new IllegalStateException("Signature (CloudHSM/KMS) Certificates not found!");
164 |
165 | KeyStore keyStore = KeyStoreUtil.getKeyStore(new File(workSslDir + "/sig.jks"), "secret");
166 | KeyStore.PrivateKeyEntry privateKeyStoreEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("sig", new KeyStore.PasswordProtection("secret".toCharArray()));
167 | KeyStore trustStore = KeyStoreUtil.generateTrustStore("psp", trustCertificates);
168 |
169 | this.xmlSigner = new XmlSigner(privateKeyStoreEntry.getPrivateKey(), (X509Certificate) privateKeyStoreEntry.getCertificate(), trustStore);
170 | this.iso20022XmlSigner = new Iso20022XmlSigner(privateKeyStoreEntry.getPrivateKey(), (X509Certificate) privateKeyStoreEntry.getCertificate(), trustStore);
171 | }
172 |
173 | @RequiredArgsConstructor
174 | public static class SpiProcessor implements Processor {
175 |
176 | private final XmlSigner xmlSigner;
177 |
178 | @Override
179 | public void process(Exchange exchange) throws Exception {
180 | final String body = exchange.getIn().getBody(String.class);
181 | if (body != null && body.length() > 0 && !xmlSigner.verify(body)) {
182 | exchange.getIn().setHeader("CamelHttpResponseCode", 403);
183 | exchange.getIn().setBody("Signature invalid!");
184 | return;
185 | }
186 |
187 | exchange.getIn().setHeader("CamelHttpResponseCode", 201);
188 | exchange.getIn().setHeader("PI-ResourceId", UUID.randomUUID().toString());
189 | exchange.getIn().setBody(null);
190 | }
191 | }
192 |
193 | public static class DictProcessor implements Processor {
194 |
195 | private final XmlSigner xmlSigner;
196 | private final String xmlResponse;
197 |
198 | @SneakyThrows
199 | public DictProcessor(XmlSigner xmlSigner, String workSslDir) {
200 | this.xmlSigner = xmlSigner;
201 | this.xmlResponse = FileUtils.readFileToString(new File(workSslDir + "/dict-response.xml"), "UTF-8");
202 | }
203 |
204 | @Override
205 | public void process(Exchange exchange) throws Exception {
206 | final String body = exchange.getIn().getBody(String.class);
207 | if (body != null && body.length() > 0 && !xmlSigner.verify(body)) {
208 | exchange.getIn().setHeader("CamelHttpResponseCode", 403);
209 | exchange.getIn().setBody("Signature invalid!");
210 | return;
211 | }
212 |
213 | exchange.getIn().setHeader("CamelHttpResponseCode", 200);
214 | exchange.getIn().setHeader("Content-Type", "application/xml;charset=utf-8");
215 |
216 | exchange.getIn().setBody(xmlSigner.sign(xmlResponse));
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/proxy/test/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | quarkus.banner.enabled=false
2 |
3 | quarkus.log.level=INFO
4 |
5 | quarkus.camel.main.routes-discovery.enabled=false
6 | camel.context.name=pix-proxy-test
7 |
8 | quarkus.package.uber-jar=true
--------------------------------------------------------------------------------