├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── flows
├── PaymentCryptographyServiceFlows-Payment Terminal Flow - P2PE.jpg
├── PaymentCryptographyServiceFlows-Pin Terminal Pin Verification Flow (DUKPT).jpg
├── PaymentCryptographyServiceFlows-Pin Terminal Set Pin Flow (PEK).jpg
├── apc-key-exchange-sequence-diagram-TR31.png
└── apc-key-exchange-sequence-diagram-TR34.png
├── java_sdk_example
├── README.md
├── pom.xml
├── run_example.sh
├── src
│ ├── main
│ │ └── java
│ │ │ └── aws
│ │ │ └── sample
│ │ │ └── paymentcryptography
│ │ │ ├── Application.java
│ │ │ ├── CommonConstants.java
│ │ │ ├── ControlPlaneUtils.java
│ │ │ ├── CreateAliasUtil.java
│ │ │ ├── DataPlaneUtils.java
│ │ │ ├── DeleteAliasUtil.java
│ │ │ ├── DeleteKeyUtil.java
│ │ │ ├── ListAliasesUtil.java
│ │ │ ├── ListKeysUtil.java
│ │ │ ├── ServiceConstants.java
│ │ │ ├── mac
│ │ │ └── MACService.java
│ │ │ ├── p2pe
│ │ │ └── PaymentProcessorService.java
│ │ │ ├── pin
│ │ │ ├── AbstractIssuerService.java
│ │ │ ├── AsyncIssuerService.java
│ │ │ ├── IssuerService.java
│ │ │ ├── PaymentProcessorPinTranslateService.java
│ │ │ └── Repository.java
│ │ │ └── terminal
│ │ │ ├── ATM.java
│ │ │ ├── AbstractTerminal.java
│ │ │ ├── PaymentTerminal.java
│ │ │ ├── PinTerminal_ISO_0_Format.java
│ │ │ ├── PinTerminal_ISO_4_Format.java
│ │ │ ├── TerminalConstants.java
│ │ │ ├── TerminalMAC.java
│ │ │ └── Utils.java
│ └── resources
│ │ └── log4j.properties
└── test-data
│ ├── sample-key-ksn-data.json
│ ├── sample-pan-arqc-key.json
│ ├── sample-pek-ksn-data-iso-0-format.json
│ ├── sample-pek-ksn-data-iso-4-format.json
│ └── sample-pin-pan.json
├── key-import-export
├── Dockerfile
├── key_exchange
│ ├── README.md
│ ├── __init__.py
│ ├── hsm
│ │ ├── atalla
│ │ │ ├── assets
│ │ │ │ └── atalla-apc-tr34-key-exchange-sequence-diagram - Key Exchange.png
│ │ │ ├── atalla_to_apc_tr31.py
│ │ │ ├── atalla_to_apc_tr34.py
│ │ │ ├── helpers
│ │ │ │ ├── apc_helper.py
│ │ │ │ ├── atalla_helper.py
│ │ │ │ ├── aws_private_ca_helper.py
│ │ │ │ └── csr_helper.py
│ │ │ ├── keys
│ │ │ │ ├── params_for_import.json
│ │ │ │ └── tr34_offline_krd_public_key_akb
│ │ │ ├── readme.md
│ │ │ └── requirements.txt
│ │ ├── futurex
│ │ │ ├── __init__.py
│ │ │ ├── commands.py
│ │ │ ├── futurex_hsm.py
│ │ │ └── sample_hsm_config.txt
│ │ └── payshield
│ │ │ ├── __init__.py
│ │ │ ├── asn_utils.py
│ │ │ ├── commands.py
│ │ │ ├── payshield_hsm.py
│ │ │ └── sample_hsm_config.txt
│ ├── import_export_ecdh.py
│ ├── import_export_tr31.py
│ ├── import_export_tr34.py
│ ├── input_config.json
│ ├── requirements.txt
│ └── utils
│ │ ├── __init__.py
│ │ ├── apc.py
│ │ └── enums.py
├── rsa
│ ├── export_app
│ │ └── export_raw_key_from_apc_with_rsa_wrap.py
│ ├── export_app_with_signature
│ │ ├── README.md
│ │ ├── buildlayer.sh
│ │ ├── cfn
│ │ │ └── template.yaml
│ │ ├── images
│ │ │ └── arch-diagram.png
│ │ ├── lambdas
│ │ │ ├── csrbuilder.py
│ │ │ └── main.py
│ │ └── requirements.txt
│ └── import_app
│ │ └── import_raw_key_into_apc_with_rsa_wrap.py
└── tr34
│ ├── README.MD
│ ├── export-app
│ └── export_raw_key_from_apc.py
│ └── import_app
│ ├── Readme.md
│ ├── apc_demo_keysetup.py
│ ├── import_tr31_raw_key_to_apc.py
│ └── import_tr34_raw_key_to_apc.py
├── payment-cryptography-terraform-samples
├── README.md
├── acquirer
│ ├── keys_awk.tf
│ └── keys_bdk.tf
├── application.tf
├── architecture.png
├── data.tf
├── issuer-cvv
│ └── keys_cvv.tf
├── issuer-pin
│ └── keys_pin.tf
├── log_archive.tf
├── main.tf
├── outputs.tf
├── providers.tf
├── pvlink.tf
├── ssm.tf
├── tfsec_output.txt
└── variables.tf
└── python_sdk_example
└── ecdh_flows
├── README.md
├── images
├── PIN-Reset.png
├── PIN-Reveal.png
└── PIN-Select.png
├── payment_crypto
├── __init__.py
├── ecdh
│ ├── __init__.py
│ ├── backend.py
│ ├── client.py
│ ├── crypto_utils.py
│ └── setup.py
├── main.py
└── tear_down.py
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | java_sdk_example/.mvn/
3 | java_sdk_example/test-data/pan_to_pin_verification.csv
4 | java_sdk_example/target
5 |
6 | # Local .terraform directories
7 | **/.terraform/*
8 |
9 | # .tfstate files
10 | *.tfstate
11 | *.tfstate.*
12 |
13 | # Crash log files
14 | crash.log
15 | crash.*.log
16 |
17 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as
18 | # password, private keys, and other secrets. These should not be part of version
19 | # control as they are data points which are potentially sensitive and subject
20 | # to change depending on the environment.
21 | *.tfvars
22 | *.tfvars.json
23 |
24 | # Ignore override files as they are usually used to override resources locally and so
25 | # are not checked in
26 | override.tf
27 | override.tf.json
28 | *_override.tf
29 | *_override.tf.json
30 |
31 | # Include override files you do wish to add to version control using negated pattern
32 | # !example_override.tf
33 |
34 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
35 | # example: *tfplan*
36 |
37 | # Ignore CLI configuration files
38 | .terraformrc
39 | terraform.rc
40 |
41 | ###
42 | .terraform.lock.hcl
43 | terraform.tf
44 |
45 | ### MacOS
46 | .DS_Store
47 |
48 | ### Python
49 | *.swp
50 | package-lock.json
51 | __pycache__
52 | .pytest_cache
53 | .venv
54 | *.egg-info
55 |
56 | ### Idea
57 | .idea
58 |
--------------------------------------------------------------------------------
/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 *main* 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 |
--------------------------------------------------------------------------------
/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 this
4 | software and associated documentation files (the "Software"), to deal in the Software
5 | without restriction, including without limitation the rights to use, copy, modify,
6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7 | 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 IMPLIED,
10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 | Using HSM based key exchange sample code will require access to Payment HSMs that is not provided and needs to be provisioned separately through relevant manufacturers or their resellers. This sample code does not imply an endorsement by any third parties mentioned.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AWS Payment Cryptography Samples
2 |
3 | This repo contains sample code for AWS Payment Cryptography Service for -
4 | - key exchange between AWS Payment Cryptography with HSMs such as [payShield 10k](key-import-export/key_exchange/), [Atalla AT1000](key-import-export/key_exchange/hsm/atalla/) and [Futurex](key-import-export/key_exchange/) using TR-34 and TR-31 protocols.
5 | - Various payment flow such as P2PE, Pin (set and verify) and Pin translation and ECDH Pin Set/Reveal
6 |
7 | Before starting, ensure that the [service is available in the region](https://aws.amazon.com/payment-cryptography/pricing/) you want to run the samples in.
8 |
9 | ## Samples
10 |
11 | ### [Key Import/Export](key-import-export/)
12 | This section contains Python based script to exchange keys between AWS Payment Cryptography Service and HSMs such as [payShield 10k](key-import-export/key_exchange/README.md), [Atalla AT1000](key-import-export/key_exchange/hsm/atalla/readme.md) and [Futurex](key-import-export/key_exchange/README.md). This process can be used for either key migration or for ongoing synchronization.
13 | Alternatively, for testing, you can also import plain text keys using either [TR-34](key-import-export/tr34/import_app/Readme.md) or [RSA](key-import-export/rsa/import_app/import_raw_key_into_apc_with_rsa_wrap.py) into AWS Payment Cryptography.
14 |
15 | #### Prerequisite
16 | Before running the [JAVA](java_sdk_example/README.md) or [Python](python_sdk_example/ecdh_flows/README.md) based sample applications, you will need to import the required keys into AWS Payment Cryptography. Refer to [instructions](key-import-export/tr34/import_app/Readme.md) for importing plain text keys before running the sample applications. **Note: This should be used in non-production testing environment only.**
17 |
18 |
19 | ### [JAVA Based Flows](java_sdk_example/README.md)
20 | This section contains flows such as P2PE, Pin set/verify and translation. Additionally, the flows are implemented using both [synchronous](java_sdk_example/src/main/java/aws/sample/paymentcryptography/pin/IssuerService.java) and [asynchronous](java_sdk_example/src/main/java/aws/sample/paymentcryptography/pin/AsyncIssuerService.java) APIs showing flexibility of AWS Payment Cryptography.
21 |
22 | ### [Python Based Flows](python_sdk_example/ecdh_flows/README.md)
23 | This section contains ECDH flow for pin set and pin reveal functionalities
24 |
25 | ## Contributing
26 |
27 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
28 |
29 | ## License
30 |
31 | This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file.
--------------------------------------------------------------------------------
/flows/PaymentCryptographyServiceFlows-Payment Terminal Flow - P2PE.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/flows/PaymentCryptographyServiceFlows-Payment Terminal Flow - P2PE.jpg
--------------------------------------------------------------------------------
/flows/PaymentCryptographyServiceFlows-Pin Terminal Pin Verification Flow (DUKPT).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/flows/PaymentCryptographyServiceFlows-Pin Terminal Pin Verification Flow (DUKPT).jpg
--------------------------------------------------------------------------------
/flows/PaymentCryptographyServiceFlows-Pin Terminal Set Pin Flow (PEK).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/flows/PaymentCryptographyServiceFlows-Pin Terminal Set Pin Flow (PEK).jpg
--------------------------------------------------------------------------------
/flows/apc-key-exchange-sequence-diagram-TR31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/flows/apc-key-exchange-sequence-diagram-TR31.png
--------------------------------------------------------------------------------
/flows/apc-key-exchange-sequence-diagram-TR34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/flows/apc-key-exchange-sequence-diagram-TR34.png
--------------------------------------------------------------------------------
/java_sdk_example/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | aws.samples
5 | aws-paymentcryptography-samples
6 | jar
7 | 1.0
8 | AWS Payment Cryptography Examples
9 | http://maven.apache.org
10 |
11 | 1.8
12 | 1.8
13 | 2.27.14
14 |
15 |
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-parent
20 | 3.1.1
21 | pom
22 | import
23 |
24 |
25 | software.amazon.awssdk
26 | bom
27 | ${aws.java.sdk.version}
28 | pom
29 | import
30 |
31 |
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-web
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-actuator
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-test
45 | test
46 |
47 |
48 | software.amazon.awssdk
49 | paymentcryptography
50 | 2.27.12
51 |
52 |
53 |
54 | software.amazon.awssdk
55 | paymentcryptographydata
56 | 2.25.45
57 |
58 |
59 | org.apache.commons
60 | commons-lang3
61 | 3.12.0
62 |
63 |
64 | org.apache.logging.log4j
65 | log4j-api
66 | 2.20.0
67 |
68 |
69 | org.apache.logging.log4j
70 | log4j-core
71 | 2.20.0
72 |
73 |
74 |
75 | commons-codec
76 | commons-codec
77 | 1.16.0
78 |
79 |
80 |
81 | org.json
82 | json
83 | 20231013
84 |
85 |
86 |
87 | org.bouncycastle
88 | bcprov-jdk15on
89 | 1.70
90 |
91 |
92 | junit
93 | junit
94 | 4.13.1
95 | test
96 |
97 |
98 |
99 |
100 |
101 |
102 | src/resources
103 |
104 |
105 |
106 |
107 | org.springframework.boot
108 | spring-boot-maven-plugin
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/java_sdk_example/run_example.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [[ -z $* ]] ; then
3 | echo 'Supply the name of one of the example classes as an argument.'
4 | echo 'If there are arguments to the class, put them in quotes after the class name.'
5 | exit 1
6 | fi
7 | export CLASSPATH=target/aws-paymentcryptography-samples-1.0.jar
8 | export className=$1
9 | echo "## Running $className..."
10 | shift
11 | echo "## arguments $@..."
12 | mvn exec:java -Dexec.mainClass="$className" -Dexec.args="$@" -Dexec.cleanupDaemonThreads=false
13 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/Application.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(Application.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/CommonConstants.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | /*
4 | * Constants for both client side terminal and server side services. In real scenario, this would be separate for client and server classes.
5 | */
6 | public interface CommonConstants {
7 |
8 | public final String MODE = "CBC";
9 |
10 | public final String HOST = "http://localhost:8080";
11 |
12 | public static final String ISSUER_SERVICE_PIN_SET_API = "/issuer/setPin/";
13 | public static final String ISSUER_SERVICE_PIN_SET_API_ASYNC = "/issuer/setPinAsync/";
14 | public static final String ISSUER_SERVICE_PIN_VERIFY_API = "/issuer/pinAuthorization/";
15 | public static final String ISSUER_SERVICE_PIN_VERIFY_API_ASYNC = "/issuer/pinAuthorizationAsync/";
16 | public static final String PIN_PROCESSOR_SERVICE_PIN_SET_API = "/pin-processor-service/setPin/";
17 | public static final String PIN_PROCESSOR_SERVICE_ISO_0_FORMAT_PIN_VERIFY_API = "/pin-processor-service/verifyPin_iso_0_format/";
18 | public static final String PIN_PROCESSOR_SERVICE_ISO_4_FORMAT_PIN_VERIFY_API = "/pin-processor-service/verifyPin_iso_4_format/";
19 | public static final String PAYMENT_PROCESSOR_SERVICE_AUTHORIZE_PAYMENT_API = "/payment-processor/authorizePayment/";
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/CreateAliasUtil.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | import java.util.concurrent.ExecutionException;
4 | import java.util.logging.Logger;
5 |
6 | import software.amazon.awssdk.services.paymentcryptography.model.Alias;
7 |
8 | public class CreateAliasUtil {
9 |
10 | public static void main(String[] args) throws InterruptedException, ExecutionException {
11 | String aliasName = String.format("alias/createalias-%d", System.currentTimeMillis());
12 | if (args.length > 0) {
13 | aliasName = args[0];
14 | }
15 | Alias alias = ControlPlaneUtils.getOrCreateAlias(aliasName);
16 | Logger.getGlobal().info(String.format("Alias name: %s", alias.aliasName()));
17 | Logger.getGlobal().info(String.format("Key ARN: %s", alias.keyArn()));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/DeleteAliasUtil.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.List;
6 | import java.util.concurrent.ExecutionException;
7 | import java.util.logging.Logger;
8 |
9 | import software.amazon.awssdk.http.SdkHttpResponse;
10 | import software.amazon.awssdk.services.paymentcryptography.PaymentCryptographyClient;
11 | import software.amazon.awssdk.services.paymentcryptography.model.Alias;
12 | import software.amazon.awssdk.services.paymentcryptography.model.DeleteAliasRequest;
13 | import software.amazon.awssdk.services.paymentcryptography.model.GetAliasRequest;
14 | import software.amazon.awssdk.services.paymentcryptography.model.GetAliasResponse;
15 | import software.amazon.awssdk.services.paymentcryptography.model.ListAliasesRequest;;
16 | /*
17 | * Usage -
18 | *
19 | * To delete all aliases
20 | * ./run_example.sh aws.sample.paymentcryptography.DeleteAliasUtil all-aliases
21 | *
22 | * OR
23 | * To delete individual alias
24 | * ./run_example.sh aws.sample.paymentcryptography.DeleteAliasUtil alias/MerchantTerminal_BDK
25 | *
26 | */
27 |
28 | public class DeleteAliasUtil {
29 |
30 | private static final PaymentCryptographyClient client = ControlPlaneUtils.getControlPlaneClient();
31 | private static final String ALL_ALIASES = "all-aliases";
32 |
33 | public static void main(String[] args) throws IllegalArgumentException, InterruptedException, ExecutionException {
34 | List aliasesToDelete = null;
35 |
36 | if (args.length > 0) {
37 | String aliasToDelete = args[0];
38 | if (aliasToDelete.equals(ALL_ALIASES)) {
39 | deleteAllAliases();
40 | } else {
41 | aliasesToDelete = new ArrayList();
42 | aliasesToDelete.addAll(Arrays.asList(aliasToDelete.split(",")));
43 | for (String aliasName : aliasesToDelete) {
44 | GetAliasResponse aliasResponse = client
45 | .getAlias(GetAliasRequest.builder().aliasName(aliasName).build());
46 | deleteAlias(aliasResponse.alias());
47 | }
48 | }
49 | }
50 | }
51 |
52 | private static void deleteAllAliases() throws InterruptedException, ExecutionException {
53 | Logger.getGlobal().info("delete all aliases...");
54 | ListAliasesRequest request = ListAliasesRequest.builder().maxResults(100).build();
55 | List aliases = client.listAliases(request).aliases();
56 | if (aliases.isEmpty()) {
57 | Logger.getGlobal().info("No aliases found");
58 | }
59 | for (Alias alias : aliases) {
60 | deleteAlias(alias);
61 | }
62 | }
63 |
64 | private static void deleteAlias(Alias alias)
65 | throws IllegalArgumentException, InterruptedException, ExecutionException {
66 | if (alias == null)
67 | throw new IllegalArgumentException("Null alias passed");
68 | deleteAlias(alias.aliasName());
69 | }
70 |
71 | private static boolean deleteAlias(String aliasName) throws InterruptedException, ExecutionException {
72 | DeleteAliasRequest deleteRequest = DeleteAliasRequest.builder().aliasName(aliasName).build();
73 | SdkHttpResponse response = client.deleteAlias(deleteRequest).sdkHttpResponse();
74 | boolean deleted = response.statusCode() == 200;
75 |
76 | if (deleted) {
77 | Logger.getGlobal().info(String.format("Alias %s deleted", aliasName));
78 | } else {
79 | Logger.getGlobal().info(String.format("Alias %s not deleted", aliasName));
80 | }
81 | return deleted;
82 | }
83 |
84 | /*
85 | * Use this method if you also want to delete key of the alias - while deleting
86 | * the alias
87 | */
88 | /*
89 | * private static boolean deleteKey(String keyARN) {
90 | * Logger.getGlobal().info("deleting key " + keyARN);
91 | * DeleteKeyRequest deleteKeyRequest = new
92 | * DeleteKeyRequest().withKeyIdentifier(keyARN);
93 | * DeleteKeyResult deleteKeyResult =
94 | * ControlPlaneUtils.getControlPlaneClient().deleteKey(deleteKeyRequest);
95 | * if (deleteKeyResult.getSdkHttpMetadata().getHttpStatusCode() == 200) {
96 | * Logger.getGlobal().info(String.format("Key %s deleted", keyARN));
97 | * } else {
98 | * Logger.getGlobal().info(String.format("Key %s not deleted", keyARN));
99 | * }
100 | * return deleteKeyResult.getSdkHttpMetadata().getHttpStatusCode() == 200;
101 | * }
102 | */
103 | }
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/DeleteKeyUtil.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.List;
6 | import java.util.concurrent.ExecutionException;
7 | import java.util.logging.Level;
8 | import java.util.logging.Logger;
9 |
10 | import software.amazon.awssdk.http.SdkHttpResponse;
11 | import software.amazon.awssdk.services.paymentcryptography.PaymentCryptographyClient;
12 | import software.amazon.awssdk.services.paymentcryptography.model.DeleteKeyRequest;
13 | import software.amazon.awssdk.services.paymentcryptography.model.KeySummary;
14 | import software.amazon.awssdk.services.paymentcryptography.model.ListKeysRequest;
15 |
16 | /*
17 | * Usage -
18 | *
19 | * To Delete All Keys -
20 | * ./run_example.sh aws.sample.paymentcryptography.DeleteKey all-keys
21 | *
22 | * OR to delete individual key
23 | * ./run_example.sh aws.sample.paymentcryptography.DeleteKey arn:aws:payment-cryptography:us-east-1:XXXXXXXXXX:key/jvljh5wzjhvgadyy
24 | */
25 | public class DeleteKeyUtil {
26 |
27 | private static final PaymentCryptographyClient client = ControlPlaneUtils.getControlPlaneClient();
28 | private static final String ALL_KEYS = "all-keys";
29 |
30 | public static void main(String[] args) throws InterruptedException, ExecutionException {
31 | List keysToDelete = null;
32 |
33 | if (args.length > 0) {
34 | String arg = args[0];
35 | if (arg.equals(ALL_KEYS)) {
36 | deleteAllKeys();
37 | } else {
38 | keysToDelete = new ArrayList();
39 | keysToDelete.addAll(Arrays.asList(arg.split(",")));
40 | for (String keyName : keysToDelete) {
41 | deleteKey(keyName);
42 | }
43 | }
44 | } else {
45 | Logger.getGlobal().log(Level.INFO,"No keys passed to delete");
46 | }
47 | }
48 |
49 | private static void deleteAllKeys() throws InterruptedException, ExecutionException {
50 | ListKeysRequest request = ListKeysRequest.builder().maxResults(100).build();
51 | List keySummaries = client.listKeys(request).keys();
52 | if (keySummaries.size() == 0) {
53 | Logger.getGlobal().log(Level.INFO,"No keys to delete");
54 | return;
55 | }
56 | for (KeySummary keySummary : keySummaries) {
57 | // Only delete keys that are in CREATE_COMPLETE state and skip the ones that are
58 | // in pending deletion state etc.
59 | if (keySummary.keyState().toString().equals("CREATE_COMPLETE")) {
60 | Logger.getGlobal().log(Level.INFO,"Attempting to delete key {0}" , keySummary.keyArn());
61 | deleteKey(keySummary);
62 | Logger.getGlobal().log(Level.INFO,"Deleted key {0}" , keySummary.keyArn());
63 | }else {
64 | Logger.getGlobal().log(Level.INFO,"Skipping key {0} since it's key state is {1}" , new Object[] {keySummary.keyArn(),keySummary.keyState()});
65 | }
66 | }
67 | }
68 |
69 | private static boolean deleteKey(KeySummary keySummary)
70 | throws IllegalArgumentException, InterruptedException, ExecutionException {
71 | if (keySummary == null)
72 | throw new IllegalArgumentException("Null key summary passed");
73 | return deleteKey(keySummary.keyArn());
74 | }
75 |
76 | private static boolean deleteKey(String keyARN) throws InterruptedException, ExecutionException {
77 | DeleteKeyRequest deleteRequest = DeleteKeyRequest.builder().keyIdentifier(keyARN).build();
78 | SdkHttpResponse deleteResponse = client.deleteKey(deleteRequest).sdkHttpResponse();
79 | boolean deleted = deleteResponse.statusCode() == 200;
80 | if (deleted) {
81 | Logger.getGlobal().log(Level.INFO,String.format("Key %s deleted", keyARN));
82 | } else {
83 | Logger.getGlobal().log(Level.INFO,String.format("Key %s not deleted", keyARN));
84 | }
85 | return deleted;
86 | }
87 | }
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/ListAliasesUtil.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.ExecutionException;
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 |
8 | import software.amazon.awssdk.services.paymentcryptography.PaymentCryptographyClient;
9 | import software.amazon.awssdk.services.paymentcryptography.model.Alias;
10 | import software.amazon.awssdk.services.paymentcryptography.model.ListAliasesRequest;
11 |
12 | /*
13 | * Usage -
14 | *
15 | * ./run_example.sh aws.sample.paymentcryptography.ListAliasesUtil
16 | *
17 | */
18 |
19 | public class ListAliasesUtil {
20 |
21 | public static void main(String[] args) throws InterruptedException, ExecutionException {
22 | PaymentCryptographyClient client = ControlPlaneUtils.getControlPlaneClient();
23 | ListAliasesRequest request = ListAliasesRequest.builder().build();
24 | List aliases = client.listAliases(request).aliases();
25 | if (aliases.size() == 0) {
26 | Logger.getGlobal().log(Level.INFO,"No aliases found");
27 | return;
28 | }
29 | for (Alias alias : aliases) {
30 | Logger.getGlobal().log(Level.INFO,"{0} : {1}", new Object[] {alias.aliasName(), alias.keyArn()});
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/ListKeysUtil.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.ExecutionException;
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 |
8 | import software.amazon.awssdk.services.paymentcryptography.PaymentCryptographyClient;
9 | import software.amazon.awssdk.services.paymentcryptography.model.Key;
10 | import software.amazon.awssdk.services.paymentcryptography.model.KeyAttributes;
11 | import software.amazon.awssdk.services.paymentcryptography.model.KeySummary;
12 | import software.amazon.awssdk.services.paymentcryptography.model.ListKeysRequest;
13 |
14 |
15 | /*
16 | * Usage -
17 | *
18 | * ./run_example.sh aws.sample.paymentcryptography.ListKeysUtil
19 | *
20 | */
21 | public class ListKeysUtil {
22 |
23 | public static void main(String[] args) throws InterruptedException, ExecutionException {
24 | PaymentCryptographyClient client = ControlPlaneUtils.getControlPlaneClient();
25 | ListKeysRequest request = ListKeysRequest.builder().build();
26 | List keys = client.listKeys(request).keys();
27 | if (keys.size() == 0) {
28 | Logger.getGlobal().log(Level.INFO,"No keys found");
29 | return;
30 | }
31 |
32 | for (KeySummary key : keys) {
33 | Key fullKey = ControlPlaneUtils.getKey(key.keyArn());
34 | KeyAttributes attrs = fullKey.keyAttributes();
35 | Logger.getGlobal().log(Level.INFO,String.format(
36 | "%s (%s / %s / %s)",
37 | key.keyArn(),
38 | attrs.keyClass(),
39 | attrs.keyAlgorithm(),
40 | attrs.keyUsage()));
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/ServiceConstants.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography;
2 |
3 | public interface ServiceConstants extends CommonConstants {
4 |
5 | public final String ISO_0_PIN_BLOCK_FORMAT = "ISO_FORMAT_0";
6 | public final String ISO_4_PIN_BLOCK_FORMAT = "ISO_FORMAT_4";
7 |
8 | public final String BDK_ALIAS_AES_128 = "alias/MerchantTerminal_BDK_AES_128";
9 | public final String BDK_ALIAS_TDES_2KEY = "alias/MerchantTerminal_TDES_BDK";
10 | public final String PIN_TRANSLATION_KEY_ALIAS = "alias/pinTranslateServicePek";
11 | public final String ISSUER_PEK_ALIAS = "alias/issuerPek";
12 | public final String PIN_VALIDATION_KEY_ALIAS = "alias/issuerPinValidationKey";
13 | public final String ARQC_Retail_9797_3_KEY_ALIAS = "alias/arqcValidationKey";
14 | public final String ISO_9797_3_MAC_KEY_ALIAS = "alias/macValidationKey";
15 |
16 | public final String BDK_ALGORITHM_AES_128 = "AES_128";//"TDES_2KEY";
17 | public final String BDK_ALGORITHM_TDES_2KEY = "TDES_2KEY";//"TDES_2KEY";
18 | public final String PEK_ALGORITHM = "TDES_3KEY";
19 | public final String PGK_ALGORITHM = "TDES_3KEY";
20 |
21 | public final int PIN_VERIFICATION_KEY_INDEX = 1;
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/mac/MACService.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.mac;
2 |
3 | import java.util.concurrent.ExecutionException;
4 | import java.util.logging.Level;
5 | import java.util.logging.Logger;
6 |
7 | import org.apache.commons.codec.binary.Hex;
8 | import org.springframework.stereotype.Component;
9 |
10 | import aws.sample.paymentcryptography.ControlPlaneUtils;
11 | import aws.sample.paymentcryptography.DataPlaneUtils;
12 | import aws.sample.paymentcryptography.ServiceConstants;
13 | import software.amazon.awssdk.services.paymentcryptography.model.Alias;
14 | import software.amazon.awssdk.services.paymentcryptography.model.CreateKeyRequest;
15 | import software.amazon.awssdk.services.paymentcryptography.model.Key;
16 | import software.amazon.awssdk.services.paymentcryptography.model.KeyAlgorithm;
17 | import software.amazon.awssdk.services.paymentcryptography.model.KeyAttributes;
18 | import software.amazon.awssdk.services.paymentcryptography.model.KeyClass;
19 | import software.amazon.awssdk.services.paymentcryptography.model.KeyModesOfUse;
20 | import software.amazon.awssdk.services.paymentcryptography.model.KeyUsage;
21 | import software.amazon.awssdk.services.paymentcryptographydata.model.GenerateMacRequest;
22 | import software.amazon.awssdk.services.paymentcryptographydata.model.GenerateMacResponse;
23 | import software.amazon.awssdk.services.paymentcryptographydata.model.MacAlgorithm;
24 | import software.amazon.awssdk.services.paymentcryptographydata.model.MacAttributes;
25 | import software.amazon.awssdk.services.paymentcryptographydata.model.VerifyMacRequest;
26 | import software.amazon.awssdk.services.paymentcryptographydata.model.VerifyMacResponse;
27 | import software.amazon.awssdk.utils.StringUtils;
28 |
29 | @Component
30 | public class MACService {
31 |
32 | public String getMACKey() throws InterruptedException, ExecutionException {
33 | Alias iso9797MacKeyAlias = ControlPlaneUtils.getOrCreateAlias(ServiceConstants.ISO_9797_3_MAC_KEY_ALIAS);
34 |
35 | if (!StringUtils.isBlank(iso9797MacKeyAlias.keyArn())) {
36 | return iso9797MacKeyAlias.keyArn();
37 | }
38 |
39 | KeyModesOfUse modes = KeyModesOfUse
40 | .builder()
41 | .generate(true)
42 | .verify(true)
43 | .build();
44 | KeyAttributes attributes = KeyAttributes
45 | .builder()
46 | .keyAlgorithm(KeyAlgorithm.TDES_2_KEY)
47 | .keyClass(KeyClass.SYMMETRIC_KEY)
48 | .keyUsage(KeyUsage.TR31_M3_ISO_9797_3_MAC_KEY)
49 | .keyModesOfUse(modes)
50 | .build();
51 |
52 | CreateKeyRequest request = CreateKeyRequest.builder()
53 | .keyAttributes(attributes)
54 | .enabled(true)
55 | .exportable(true)
56 | .build();
57 |
58 | Key key = ControlPlaneUtils.getControlPlaneClient().createKey(request).key();
59 | ControlPlaneUtils.upsertAlias(iso9797MacKeyAlias.aliasName(), key.keyArn());
60 | return iso9797MacKeyAlias.aliasName();
61 | }
62 |
63 | public String generateMac(String text) throws InterruptedException, ExecutionException {
64 | String macKeyArn = getMACKey();
65 | GenerateMacResponse macGenerateResponse = generateMac(macKeyArn,text);
66 | return macGenerateResponse.mac();
67 | }
68 |
69 | public GenerateMacResponse generateMac(String macKeyArn, String text) {
70 | MacAttributes macAttributes = MacAttributes
71 | .builder()
72 | .algorithm(MacAlgorithm.ISO9797_ALGORITHM3)
73 | .build();
74 |
75 | Logger.getGlobal().log(Level.INFO,"MACService:generateMac Attempting to generate MAC thru AWS Cryptography Service for text {0}", text);
76 | GenerateMacRequest generateMacRequest = GenerateMacRequest
77 | .builder()
78 | .keyIdentifier(macKeyArn)
79 | .messageData(Hex.encodeHexString(text.getBytes()))
80 | .generationAttributes(macAttributes)
81 | .build();
82 |
83 | GenerateMacResponse macGenerateResponse = DataPlaneUtils.getDataPlaneClient().generateMac(generateMacRequest);
84 | Logger.getGlobal().log(Level.INFO,"MACService:generateMac MAC generation successfult for {0}. MAC is {1}", new Object[] {text,macGenerateResponse.mac()});
85 | return macGenerateResponse;
86 | }
87 |
88 | public VerifyMacResponse getMacVerification(String macKeyArn, String mac) {
89 | MacAttributes macAttributes = MacAttributes
90 | .builder()
91 | .algorithm(MacAlgorithm.ISO9797_ALGORITHM3)
92 | .build();
93 |
94 | VerifyMacRequest verifyMacRequest = VerifyMacRequest
95 | .builder()
96 | .keyIdentifier(macKeyArn)
97 | .verificationAttributes(macAttributes)
98 | .mac(mac)
99 | .messageData(mac)
100 | .build();
101 | VerifyMacResponse macVerificationResponse = DataPlaneUtils.getDataPlaneClient().verifyMac(verifyMacRequest);
102 | return macVerificationResponse;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/p2pe/PaymentProcessorService.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.p2pe;
2 |
3 | import java.util.logging.Level;
4 | import java.util.logging.Logger;
5 |
6 | import org.apache.commons.codec.DecoderException;
7 | import org.apache.commons.codec.binary.Hex;
8 | import org.apache.commons.lang3.RandomUtils;
9 | import org.bouncycastle.crypto.InvalidCipherTextException;
10 | import org.bouncycastle.crypto.paddings.PKCS7Padding;
11 | import org.json.JSONObject;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.web.bind.annotation.GetMapping;
14 | import org.springframework.web.bind.annotation.RequestParam;
15 | import org.springframework.web.bind.annotation.ResponseBody;
16 | import org.springframework.web.bind.annotation.RestController;
17 |
18 | import aws.sample.paymentcryptography.DataPlaneUtils;
19 | import aws.sample.paymentcryptography.ServiceConstants;
20 | import aws.sample.paymentcryptography.mac.MACService;
21 | import software.amazon.awssdk.services.paymentcryptographydata.PaymentCryptographyDataClient;
22 | import software.amazon.awssdk.services.paymentcryptographydata.model.DecryptDataRequest;
23 | import software.amazon.awssdk.services.paymentcryptographydata.model.DecryptDataResponse;
24 | import software.amazon.awssdk.services.paymentcryptographydata.model.DukptEncryptionAttributes;
25 | import software.amazon.awssdk.services.paymentcryptographydata.model.EncryptionDecryptionAttributes;
26 |
27 | @RestController
28 | public class PaymentProcessorService {
29 |
30 | @Autowired
31 | private MACService macService;
32 |
33 | // GET API for simplicity. In production scenarios, this would typically be a POST API
34 | @GetMapping(ServiceConstants.PAYMENT_PROCESSOR_SERVICE_AUTHORIZE_PAYMENT_API)
35 | @ResponseBody
36 | public String authorizePayment(@RequestParam String encryptedData, @RequestParam String ksn) {
37 | try {
38 | PaymentCryptographyDataClient dataPlaneClient = DataPlaneUtils.getDataPlaneClient();
39 |
40 | DukptEncryptionAttributes dukptEncryptionAttributes = DukptEncryptionAttributes
41 | .builder()
42 | .keySerialNumber(ksn)
43 | .mode(ServiceConstants.MODE)
44 | .build();
45 |
46 | EncryptionDecryptionAttributes decryptionAttributes = EncryptionDecryptionAttributes
47 | .builder()
48 | .dukpt(dukptEncryptionAttributes)
49 | .build();
50 |
51 | DecryptDataRequest decryptDataRequest = DecryptDataRequest
52 | .builder()
53 | .cipherText(encryptedData)
54 | .keyIdentifier(ServiceConstants.BDK_ALIAS_TDES_2KEY)
55 | .decryptionAttributes(decryptionAttributes)
56 | .build();
57 |
58 | Logger.getGlobal()
59 | .log(Level.INFO,"PaymentProcessorService:authorizePayment Attempting to decrypt data {0}" ,encryptedData);
60 | DecryptDataResponse decryptDataResponse = dataPlaneClient.decryptData(decryptDataRequest);
61 |
62 | int padCount = getPADCount(decryptDataResponse.plainText());
63 | String decryptedText = decryptDataResponse.plainText().substring(0,
64 | decryptDataResponse.plainText().length() - padCount * 2);
65 | String textWithPaddingRemoved = new String(Hex.decodeHex(decryptedText));
66 | JSONObject responseJsonObject = new JSONObject()
67 | .put("response", textWithPaddingRemoved)
68 | .put("authCode", getApprovalCode())
69 | .put("response_code", getResponseCode());
70 |
71 | String macData = getMACService().generateMac(responseJsonObject.toString());
72 |
73 | JSONObject returnJsonObject = new JSONObject()
74 | .put("mac", macData)
75 | .put("response", responseJsonObject.toString());
76 | Logger.getGlobal().log(Level.INFO,
77 | "PaymentProcessorService:authorizePayment Decryption completed - {0}"
78 | ,responseJsonObject.toString());
79 | return returnJsonObject.toString();
80 | } catch (Exception exception) {
81 | Logger.getGlobal().log(Level.INFO,
82 | "PaymentProcessorService:authorizePayment Error occurred when decrypting");
83 | JSONObject returnJsonObject = new JSONObject()
84 | .put("response", exception.getMessage())
85 | .put("mac", "");
86 | exception.printStackTrace();
87 | return returnJsonObject.toString();
88 | }
89 | }
90 |
91 | public MACService getMACService() {
92 | return macService;
93 | }
94 |
95 | public void setmacService(MACService macService) {
96 | this.macService = macService;
97 | }
98 |
99 | private int getPADCount(String data) throws InvalidCipherTextException, DecoderException {
100 | PKCS7Padding padder = new PKCS7Padding();
101 | int padCount = padder.padCount(Hex.decodeHex(data));
102 | return padCount;
103 | }
104 |
105 | /*
106 | * Returns a random 3-byte hex string for the approval code.
107 | */
108 | private String getApprovalCode() {
109 | byte[] approvalCode = RandomUtils.nextBytes(3);
110 |
111 | String hexApprovalCode = "";
112 |
113 | // Iterating through each byte in the array
114 | for (byte i : approvalCode) {
115 | hexApprovalCode += String.format("%02X", i);
116 | }
117 | return hexApprovalCode;
118 | }
119 |
120 | /*
121 | * Returns a random 2-byte hex string for the response code.
122 | */
123 | private String getResponseCode() {
124 | return RandomUtils.nextInt(0, 9) + "" + RandomUtils.nextInt(0, 9);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/pin/AbstractIssuerService.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.pin;
2 |
3 | import java.util.concurrent.ExecutionException;
4 |
5 | import org.json.JSONObject;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.web.bind.annotation.RequestParam;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | import aws.sample.paymentcryptography.ServiceConstants;
11 | import software.amazon.awssdk.services.paymentcryptography.model.Alias;
12 | import software.amazon.awssdk.services.paymentcryptographydata.model.MajorKeyDerivationMode;
13 | import software.amazon.awssdk.services.paymentcryptographydata.model.PinVerificationAttributes;
14 | import software.amazon.awssdk.services.paymentcryptographydata.model.SessionKeyAmex;
15 | import software.amazon.awssdk.services.paymentcryptographydata.model.SessionKeyDerivation;
16 | import software.amazon.awssdk.services.paymentcryptographydata.model.VerifyAuthRequestCryptogramRequest;
17 | import software.amazon.awssdk.services.paymentcryptographydata.model.VerifyPinDataRequest;
18 | import software.amazon.awssdk.services.paymentcryptographydata.model.VisaPinVerification;
19 |
20 | @RestController
21 | public abstract class AbstractIssuerService {
22 |
23 | protected Alias issuerPekAlias = null;
24 | protected Alias pinValidationKeyAlias = null;
25 | protected Alias arqcValidationKeyAlias = null;
26 | protected String PAN_SEQUENCE_NUMBER = "00";
27 | protected String HEX_REGEX = "^[0-9A-Fa-f]+$";
28 |
29 | protected enum RETURN_REASON_CODES {
30 | APPROVED,
31 | DECLINED,
32 | INSUFFICIENT_FUNDS
33 | };
34 |
35 | protected AbstractIssuerService() throws InterruptedException, ExecutionException {
36 | }
37 | /* File based repository where the pin verification values (PVV) are store against the PAN.
38 | * The PVV is needed for PIN verification with the AWS Cryptography Service. In real scenario,
39 | * the PVV would be stored in a database.
40 | */
41 | @Autowired
42 | protected Repository repository;
43 |
44 | public abstract String setPinData(String encryptedPinBLock, @RequestParam String pan);
45 |
46 | public abstract String pinAuthorizationFlow( String encryptedPin, @RequestParam String pan, @RequestParam String transactionData, @RequestParam String arqcCryptogram);
47 |
48 | protected VerifyPinDataRequest getVerifyPinDataRequest(String encryptedPinBlock, String encryptionKeyIdentifier,
49 | String verificationKeyIdentifer, String pinVerificationValue, String pinBlockFormat,
50 | String primaryAccountNumber) {
51 |
52 | VisaPinVerification visaPinVerification = VisaPinVerification
53 | .builder()
54 | .pinVerificationKeyIndex(ServiceConstants.PIN_VERIFICATION_KEY_INDEX)
55 | .verificationValue(pinVerificationValue)
56 | .build();
57 | PinVerificationAttributes pinVerificationAttributes = PinVerificationAttributes
58 | .builder()
59 | .visaPin(visaPinVerification)
60 | .build();
61 |
62 | VerifyPinDataRequest verifyPinDataRequest = VerifyPinDataRequest
63 | .builder().encryptedPinBlock(encryptedPinBlock)
64 | .verificationKeyIdentifier(verificationKeyIdentifer)
65 | .encryptionKeyIdentifier(encryptionKeyIdentifier)
66 | .primaryAccountNumber(primaryAccountNumber)
67 | .pinBlockFormat(pinBlockFormat)
68 | .verificationAttributes(pinVerificationAttributes)
69 | .build();
70 | return verifyPinDataRequest;
71 | }
72 |
73 | protected VerifyAuthRequestCryptogramRequest getVerifyARQCCryptogramRequest(String arqcCryptogram,
74 | String transactionData, String pan) {
75 | SessionKeyAmex amexAttributes = SessionKeyAmex.builder()
76 | .primaryAccountNumber(pan)
77 | .panSequenceNumber(PAN_SEQUENCE_NUMBER)
78 | .build();
79 |
80 | // Build session key derivation attributes
81 | SessionKeyDerivation derivationAttributes = SessionKeyDerivation.builder()
82 | .amex(amexAttributes)
83 | .build();
84 |
85 | // Create the verification request
86 | VerifyAuthRequestCryptogramRequest request = VerifyAuthRequestCryptogramRequest.builder()
87 | .authRequestCryptogram(arqcCryptogram)
88 | .keyIdentifier(arqcValidationKeyAlias.keyArn())
89 | .majorKeyDerivationMode(MajorKeyDerivationMode.EMV_OPTION_A)
90 | .transactionData(transactionData)
91 | .sessionKeyDerivationAttributes(derivationAttributes)
92 | .build();
93 |
94 | return request;
95 | }
96 |
97 | protected JSONObject validateTransaction(String transactionData) throws Exception {
98 | JSONObject response = new JSONObject();
99 | float authAmount = getAmount(transactionData);
100 | if (authAmount > 1000) {
101 | response.put("status",RETURN_REASON_CODES.DECLINED.name());
102 | response.put("reason", "Transaction over limit");
103 | }else {
104 | response.put("status", RETURN_REASON_CODES.APPROVED.name());
105 | }
106 | return response;
107 | }
108 |
109 | protected float getAmount(String transactionData) throws IllegalArgumentException{
110 | if(!isValidHexString(transactionData)) {
111 | throw new IllegalArgumentException("Transaction data not in hex format");
112 | }
113 | String amountString = transactionData.substring(0,12);
114 | return Float.parseFloat(amountString) / 100;
115 | }
116 |
117 | // Utility method to validate hex string
118 | protected boolean isValidHexString(String hex) {
119 | return hex != null && hex.matches(HEX_REGEX);
120 | }
121 |
122 | protected Repository getRepository() {
123 | return repository;
124 | }
125 |
126 | protected void setRepository(Repository repository) {
127 | this.repository = repository;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/pin/PaymentProcessorPinTranslateService.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.pin;
2 |
3 | import java.util.concurrent.ExecutionException;
4 | import java.util.logging.Level;
5 | import java.util.logging.Logger;
6 |
7 | import org.springframework.http.ResponseEntity;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.RequestParam;
10 | import org.springframework.web.bind.annotation.ResponseBody;
11 | import org.springframework.web.bind.annotation.RestController;
12 | import org.springframework.web.client.RestTemplate;
13 |
14 | import aws.sample.paymentcryptography.ControlPlaneUtils;
15 | import aws.sample.paymentcryptography.DataPlaneUtils;
16 | import aws.sample.paymentcryptography.ServiceConstants;
17 | import software.amazon.awssdk.services.paymentcryptography.model.Alias;
18 | import software.amazon.awssdk.services.paymentcryptography.model.Key;
19 | import software.amazon.awssdk.services.paymentcryptographydata.model.TranslatePinDataResponse;
20 | import software.amazon.awssdk.utils.StringUtils;
21 |
22 | @RestController
23 | public class PaymentProcessorPinTranslateService {
24 |
25 | // GET API for simplicity. In production scenarios, this would typically be a POST API
26 | @GetMapping(ServiceConstants.PIN_PROCESSOR_SERVICE_ISO_0_FORMAT_PIN_VERIFY_API)
27 | @ResponseBody
28 | public String verifyPinData_ISO_0_Format(@RequestParam String encryptedPin, @RequestParam String pan, @RequestParam String ksn, @RequestParam String transactionData, @RequestParam String arqcCryptogram) throws InterruptedException, ExecutionException {
29 |
30 | Logger.getGlobal().log(Level.INFO,"PaymentProcessorPinTranslateService:verifyPinData_ISO_0_Format Attempting to translate BDK encrypted PIN block {0} to PEK encrypted PIN Block",encryptedPin);
31 | String acquirerWorkingKeyArn = getAcquirerWorkingKeyArn();
32 | TranslatePinDataResponse translatePinDataResponse = DataPlaneUtils.translateVisaPinBlockBdkToPek(
33 | ServiceConstants.BDK_ALIAS_TDES_2KEY,
34 | ServiceConstants.ISO_0_PIN_BLOCK_FORMAT,
35 | encryptedPin,
36 | acquirerWorkingKeyArn,
37 | ServiceConstants.ISO_0_PIN_BLOCK_FORMAT,
38 | ServiceConstants.BDK_ALGORITHM_TDES_2KEY,
39 | ksn,
40 | pan);
41 |
42 | Logger.getGlobal().log(Level.INFO,"PaymentProcessorPinTranslateService:verifyPinData_ISO_0_Format incoming pin block {0} translted to pin block {1}", new Object[] {encryptedPin,translatePinDataResponse.pinBlock()});
43 | RestTemplate restTemplate = new RestTemplate();
44 | String verifyPinUrl = ServiceConstants.HOST
45 | + ServiceConstants.ISSUER_SERVICE_PIN_VERIFY_API_ASYNC;
46 | String finalVerifyPinlUrl = new StringBuilder(verifyPinUrl)
47 | .append("?encryptedPin=")
48 | .append(translatePinDataResponse.pinBlock())
49 | .append("&pan=")
50 | .append(pan)
51 | .append("&transactionData=")
52 | .append(transactionData)
53 | .append("&arqcCryptogram=")
54 | .append(arqcCryptogram)
55 | .toString();
56 |
57 | ResponseEntity verifyPinResponse = restTemplate.getForEntity(finalVerifyPinlUrl, String.class);
58 | return verifyPinResponse.getBody();
59 | }
60 |
61 | // GET API for simplicity. In production scenarios, this would typically be a POST API
62 | @GetMapping(ServiceConstants.PIN_PROCESSOR_SERVICE_ISO_4_FORMAT_PIN_VERIFY_API)
63 | @ResponseBody
64 | public String verifyPinData_ISO_4_Format(@RequestParam String encryptedPin, @RequestParam String pan, @RequestParam String ksn, @RequestParam String transactionData, @RequestParam String arqcCryptogram) throws InterruptedException, ExecutionException {
65 |
66 | Logger.getGlobal().log(Level.INFO,"PaymentProcessorPinTranslateService:verifyPinData_ISO_4_Format Attempting to translate BDK encrypted PIN block {0} to PEK encrypted PIN Block" + encryptedPin);
67 | String acquirerWorkingKeyArn = getAcquirerWorkingKeyArn();
68 | TranslatePinDataResponse translatePinDataResponse = DataPlaneUtils.translateVisaPinBlockBdkToPek(
69 | ServiceConstants.BDK_ALIAS_AES_128,
70 | ServiceConstants.ISO_4_PIN_BLOCK_FORMAT,
71 | encryptedPin,
72 | acquirerWorkingKeyArn,
73 | ServiceConstants.ISO_0_PIN_BLOCK_FORMAT,
74 | ServiceConstants.BDK_ALGORITHM_AES_128,
75 | ksn,
76 | pan);
77 |
78 | Logger.getGlobal().log(Level.INFO,"PaymentProcessorPinTranslateService:verifyPinData_ISO_4_Format BDK PIN {0} to PEK encrypted PIN Block {1} translation is successful", new Object[] {encryptedPin,translatePinDataResponse.pinBlock()});
79 | RestTemplate restTemplate = new RestTemplate();
80 | String verifyPinUrl = ServiceConstants.HOST
81 | + ServiceConstants.ISSUER_SERVICE_PIN_VERIFY_API_ASYNC;
82 | String finalVerifyPinlUrl = new StringBuilder(verifyPinUrl)
83 | .append("?encryptedPin=")
84 | .append(translatePinDataResponse.pinBlock())
85 | .append("&pan=")
86 | .append(pan)
87 | .append("&transactionData=")
88 | .append(transactionData)
89 | .append("&arqcCryptogram=")
90 | .append(arqcCryptogram)
91 | .toString();
92 |
93 | ResponseEntity verifyPinResponse = restTemplate.getForEntity(finalVerifyPinlUrl, String.class);
94 | return verifyPinResponse.getBody();
95 | }
96 |
97 | /*
98 | * Creating/Retrieving the Acquirer Working Key (AWK) alias. The underlying key
99 | * is the same as the DEMO_PIN_PEK_ALIAS.
100 | * In real scenario, the payment gateway and acquirer would have the same PEK
101 | * through a key exchange process.
102 | */
103 | private static String getAcquirerWorkingKeyArn() throws InterruptedException, ExecutionException {
104 | Alias acquirerWorkingKeyAlias = ControlPlaneUtils.getOrCreateAlias(ServiceConstants.PIN_TRANSLATION_KEY_ALIAS);
105 | if (StringUtils.isBlank(acquirerWorkingKeyAlias.keyArn())) {
106 | Logger.getGlobal().log(Level.INFO,"No AWS PEK found, creating a new one.");
107 | Key acquirerWorkingKey = ControlPlaneUtils.createPEK(ServiceConstants.PEK_ALGORITHM);
108 | acquirerWorkingKeyAlias = ControlPlaneUtils.upsertAlias(acquirerWorkingKeyAlias.aliasName(),
109 | acquirerWorkingKey.keyArn());
110 | Logger.getGlobal().log(Level.INFO,String.format("PEK created: {0}", acquirerWorkingKeyAlias.keyArn()));
111 | return acquirerWorkingKeyAlias.keyArn();
112 | }
113 | return acquirerWorkingKeyAlias.keyArn();
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/pin/Repository.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.pin;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.BufferedWriter;
5 | import java.io.File;
6 | import java.io.FileReader;
7 | import java.io.FileWriter;
8 | import java.io.IOException;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 | import java.util.logging.Level;
12 | import java.util.logging.Logger;
13 |
14 | import org.springframework.stereotype.Component;
15 |
16 | @Component
17 | public class Repository {
18 |
19 | private static final String PAN_TO_PVV_FILE = System.getProperty("user.dir")
20 | + "/test-data/pan_to_pin_verification.csv";
21 | private File panToPinVerificationValueMapFile = new File(PAN_TO_PVV_FILE);
22 | private BufferedWriter writer = null;
23 | private Map panToPvvMap = null;
24 |
25 | public Repository() throws Exception {
26 | if (!getPanToPinVerificationValueMapFile().exists()) {
27 | Logger.getGlobal().log(Level.INFO,"Creating new pan to pin verification map file {0} ", getPanToPinVerificationValueMapFile().getAbsoluteFile());
28 | try {
29 | panToPinVerificationValueMapFile.createNewFile();
30 | } catch (Exception exception) {
31 | exception.printStackTrace();
32 | }
33 | }
34 | //panToPvvMap = getMapFromCSV(getPanToPinVerificationValueMapFile().getAbsolutePath());
35 | setWriter(new BufferedWriter(new FileWriter(getPanToPinVerificationValueMapFile())));
36 | }
37 |
38 | private Map getMapFromCSV(final String filePath) throws Exception {
39 | Map map = new HashMap();
40 | BufferedReader bufferedReader = null;
41 | try {
42 | bufferedReader = new BufferedReader(new FileReader(getPanToPinVerificationValueMapFile()));
43 | String line = null;
44 | // read file line by line
45 | while ((line = bufferedReader.readLine()) != null && line.trim() !="") {
46 | map.put(line.split(",")[0], line.split(",")[1]);
47 | }
48 | } catch (Exception e) {
49 | e.printStackTrace();
50 | } finally {
51 | if(bufferedReader!=null)
52 | bufferedReader.close();
53 | }
54 | return map;
55 | }
56 |
57 | public void addEntry(String pan, String pinVerificationValue) throws IOException {
58 | getWriter().append(pan + "," + pinVerificationValue);
59 | writer.newLine();
60 | getWriter().flush();
61 | }
62 |
63 | public String getEntry(String pan) throws Exception {
64 | setPanToPvvMap(getMapFromCSV(PAN_TO_PVV_FILE));
65 | return getPanToPvvMap().get(pan);
66 | }
67 |
68 | private File getPanToPinVerificationValueMapFile() {
69 | return panToPinVerificationValueMapFile;
70 | }
71 |
72 | private void setWriter(BufferedWriter writer) {
73 | this.writer = writer;
74 | }
75 |
76 | private BufferedWriter getWriter() {
77 | return writer;
78 | }
79 |
80 | public Map getPanToPvvMap() {
81 | return panToPvvMap;
82 | }
83 |
84 | public void setPanToPvvMap(Map panToPvvMap) {
85 | this.panToPvvMap = panToPvvMap;
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/ATM.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | import java.io.IOException;
4 | import java.util.logging.Level;
5 | import java.util.logging.Logger;
6 |
7 | import javax.crypto.Cipher;
8 | import javax.crypto.spec.IvParameterSpec;
9 | import javax.crypto.spec.SecretKeySpec;
10 |
11 | import org.apache.commons.codec.binary.Hex;
12 | import org.json.JSONArray;
13 | import org.json.JSONObject;
14 | import org.springframework.http.ResponseEntity;
15 | import org.springframework.web.client.RestTemplate;
16 |
17 | import aws.sample.paymentcryptography.ServiceConstants;
18 |
19 | public class ATM extends AbstractTerminal {
20 |
21 | private static final String PINS_DATA_FILE = "/test-data/sample-pin-pan.json";
22 |
23 | public static void main(String[] args) throws Exception {
24 | testPinSet();
25 | }
26 |
27 | public static void testPinSet() throws Exception {
28 | JSONObject data = loadPinAndPanData();
29 | JSONArray dataList = data.getJSONArray("pins");
30 |
31 | dataList.forEach(panPinOBject -> {
32 | try {
33 | Logger.getGlobal().log(Level.INFO,"---------testPinSet ---------");
34 | String pan = ((JSONObject) panPinOBject).getString("pan");
35 | String pin = ((JSONObject) panPinOBject).getString("pin");
36 | Logger.getGlobal().log(Level.INFO,"PAN -> {0}, PIN {1}", new Object[] {pan,pin});
37 | String encodedPin = encodeForISO0Format(pin, pan);
38 | Logger.getGlobal().log(Level.INFO,"ISO_0_Format Encoded Pin block is {0}" , encodedPin);
39 | String pekEncryptedBlock = encryptPINWithPEK(TerminalConstants.PEK, encodedPin);
40 | Logger.getGlobal().log(Level.INFO,"PEK encrypted block {0}",pekEncryptedBlock);
41 | RestTemplate restTemplate = new RestTemplate();
42 |
43 | String setPinUrl = ServiceConstants.HOST
44 | + ServiceConstants.ISSUER_SERVICE_PIN_SET_API_ASYNC;
45 |
46 | String finaSetPinlUrl = new StringBuilder(setPinUrl)
47 | .append("?encryptedPinBLock=")
48 | .append(pekEncryptedBlock)
49 | .append("&pan=")
50 | .append(pan).toString();
51 |
52 | ResponseEntity setPinResponse = restTemplate.getForEntity(finaSetPinlUrl, String.class);
53 | Logger.getGlobal().log(Level.INFO,"Response from issuer service for (PEK encrypted) pin set operation is {0}",setPinResponse.getBody());
54 | // Adding sleep to pause between requests so it's easier to read the log.
55 | Thread.sleep(sleepTimeInMs);
56 | } catch (Exception e) {
57 | e.printStackTrace();
58 | }
59 | });
60 | }
61 |
62 | public static String encryptPINWithPEK(String pek, String encodedPinBlock) throws Exception {
63 | Cipher chiper = Cipher.getInstance(TerminalConstants.TRANSFORMATION);
64 | chiper.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Hex.decodeHex(pek), TerminalConstants.ALGORITHM),
65 | new IvParameterSpec(new byte[8]));
66 |
67 | byte[] encVal = chiper.doFinal(Hex.decodeHex(encodedPinBlock));
68 | String encryptedValue = Hex.encodeHexString(encVal);
69 | return encryptedValue;
70 | }
71 |
72 | private static JSONObject loadPinAndPanData() throws IOException {
73 | return loadData(System.getProperty("user.dir") + PINS_DATA_FILE);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/AbstractTerminal.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.util.Random;
7 |
8 | import org.apache.commons.codec.DecoderException;
9 | import org.apache.commons.codec.binary.Hex;
10 | import org.apache.commons.lang3.StringUtils;
11 | import org.json.JSONObject;
12 |
13 | public abstract class AbstractTerminal {
14 |
15 | protected static String PINS_DATA_FILE = "/test-data/sample-pin-pan.json";
16 | protected static String ARQC_DATA_FILE = "/test-data/sample-pan-arqc-key.json";
17 |
18 | protected static int sleepTimeInMs = 2000;
19 | private static Random rnd = new Random();
20 |
21 | protected static JSONObject loadData(String filePath) throws IOException {
22 | File file = new File(filePath);
23 | FileInputStream fis = new FileInputStream(file);
24 | byte[] data = new byte[(int) file.length()];
25 | fis.read(data);
26 | fis.close();
27 |
28 | String paymentData = new String(data, "UTF-8");
29 | JSONObject json = new JSONObject(paymentData);
30 | return json;
31 | }
32 |
33 | protected static String getRandomNumber(int digCount) {
34 | StringBuilder sb = new StringBuilder(digCount);
35 | for (int i = 0; i < digCount; i++)
36 | sb.append((char) ('0' + rnd.nextInt(10)));
37 | return sb.toString();
38 | }
39 |
40 | /**
41 | * The PIN block is constructed by XOR-ing two 64-bit fields: the plain text PIN
42 | * field and the account number field, both of which comprise 16 four-bit
43 | * nibbles.
44 | *
45 | * The plain text PIN field is:
46 | *
47 | * one nibble with the value of 0, which identifies this as a format 0 block
48 | * one nibble encoding the length N of the PIN
49 | * N nibbles, each encoding one PIN digit
50 | * 14−N nibbles, each holding the "fill" value 15 (i.e. 11112)
51 | * The account number field is:
52 | *
53 | * four nibbles with the value of zero
54 | * 12 nibbles containing the right-most 12 digits of the primary account number
55 | * (PAN), excluding the check digit
56 | *
57 | * Decode pinblock format 0 (ISO 9564)
58 | *
59 | * @param pin pin
60 | * @param pan primary account number (PAN/CLN/CardNumber)
61 | * @return pinblock in HEX format
62 | * @throws Exception
63 | */
64 | public static String encodeForISO0Format(String pin, String pan) throws Exception {
65 | try {
66 | final String pinLenHead = StringUtils.leftPad(Integer.toString(pin.length()), 2, '0') + pin;
67 | final String pinData = StringUtils.rightPad(pinLenHead, 16, 'F');
68 | final byte[] pinToByteArray = Hex.decodeHex(pinData.toCharArray());
69 | String pan12digits = pan.substring(pan.length() - 13, pan.length() - 1);
70 | final String panData = StringUtils.leftPad(pan12digits, 16, '0');
71 | final byte[] panToByteArray = Hex.decodeHex(panData.toCharArray());
72 |
73 | final byte[] pinblock = xorBytes(pinToByteArray, panToByteArray);
74 | return Hex.encodeHexString(pinblock).toUpperCase();
75 | } catch (DecoderException e) {
76 | throw new RuntimeException("Hex decoder failed!", e);
77 | }
78 | }
79 |
80 | public static byte[] xorBytes(byte[] byteArray1, byte[] byteArray2) throws Exception {
81 | if (byteArray1.length != byteArray2.length) {
82 | throw new Exception("Two arrays are not of the same length");
83 | }
84 | byte[] output = new byte[byteArray1.length];
85 | for (int i = 0; i < byteArray1.length; i++) {
86 | output[i] = (byte) ((byteArray1[i] ^ byteArray2[i]) & 0xFF);
87 | }
88 |
89 | return output;
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/PaymentTerminal.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | import java.math.BigInteger;
4 | import java.util.logging.Level;
5 | import java.util.logging.Logger;
6 |
7 | import javax.crypto.Cipher;
8 | import javax.crypto.spec.IvParameterSpec;
9 | import javax.crypto.spec.SecretKeySpec;
10 |
11 | import org.apache.commons.codec.binary.Hex;
12 | import org.json.JSONArray;
13 | import org.json.JSONObject;
14 | import org.springframework.http.ResponseEntity;
15 | import org.springframework.web.client.RestTemplate;
16 |
17 | import aws.sample.paymentcryptography.ServiceConstants;
18 | import software.amazon.awssdk.utils.StringUtils;
19 |
20 | /*
21 | * Sample class to simulate merchant's payment terminal. This data defined in the DATA_FILE contains DUKTPT keys that are
22 | * pre generated from BDK and KSN.
23 | * This class sends the payment authorization request (similar to what termial does) to the payment service and processes
24 | * the MAC response.
25 | */
26 | public class PaymentTerminal extends AbstractTerminal {
27 | private static final String KEYS_KSN_DATA_FILE = "/test-data/sample-key-ksn-data.json";
28 |
29 | public static void main(String[] args) throws Exception {
30 | RestTemplate restTemplate = new RestTemplate();
31 | String paymentAuthorizationUrl = ServiceConstants.HOST
32 | + ServiceConstants.PAYMENT_PROCESSOR_SERVICE_AUTHORIZE_PAYMENT_API;
33 |
34 | Logger.getGlobal().log(Level.INFO,"curr dir is {0}" ,System.getProperty("user.dir"));
35 | JSONObject keyAndKSNData = loadKeyAndKSNData();
36 | JSONArray dataList = keyAndKSNData.getJSONArray("data");
37 |
38 | dataList.forEach(dataObject -> {
39 | try {
40 |
41 | Logger.getGlobal().log(Level.INFO,"--------------------------------------------------------------");
42 | String track2Data = new StringBuilder()
43 | .append(";")
44 | .append((new BigInteger(getRandomNumber(20))))
45 | .append("=")
46 | .append(new BigInteger(getRandomNumber(4)))
47 | .append("?")
48 | .toString();
49 |
50 | String encryptedData = encryptData(
51 | ((JSONObject) dataObject).get("dataKey").toString(),
52 | track2Data,
53 | ((JSONObject) dataObject).get("ksn").toString());
54 | Logger.getGlobal().log(Level.INFO,"track2data is {0}, DUKPT encrypted data is {1} " , new Object[] {track2Data,encryptedData});
55 |
56 | // Making GET calls for simplicity. In produciton scenarios these would typically be POST calls with appropriate payload.
57 | StringBuilder builder = new StringBuilder()
58 | .append("?encryptedData=")
59 | .append(encryptedData)
60 | .append("&")
61 | .append("ksn=")
62 | .append(((JSONObject) dataObject).get("ksn").toString());
63 | String finalUrl = new StringBuilder(paymentAuthorizationUrl).append(builder).toString();
64 | ResponseEntity response = restTemplate.getForEntity(finalUrl, String.class);
65 | Logger.getGlobal().log(Level.INFO,"Decrypted response from Cryptography Service - {0}",response.getBody());
66 | // Adding sleep to pause between requests so it's easier to read the log.
67 | Thread.sleep(sleepTimeInMs);
68 | JSONObject responseObject = new JSONObject(response.getBody());
69 | //Logger.getGlobal().log(Level.INFO,"response is " + responseObject.getString("response") + " mac is " + responseObject.getString("mac"));
70 | Logger.getGlobal().log(Level.INFO,"MAC Validated - {0}", validateMAC(responseObject.getString("response"),responseObject.getString("mac").toLowerCase()));
71 | } catch (Exception e) {
72 | e.printStackTrace();
73 | }
74 | });
75 | }
76 |
77 | private static boolean validateMAC(String response, String macToVerify) throws Exception {
78 | if (StringUtils.isBlank(macToVerify)) {
79 | return false;
80 | }
81 | String macOnTerminal = TerminalMAC.getMac(Hex.encodeHexString(response.getBytes()));
82 | Logger.getGlobal().log(Level.INFO,"MAC from payment service {0}, MAC from terminal {1}" , new Object[] {macToVerify,macOnTerminal});
83 | return macOnTerminal.trim().toLowerCase().startsWith(macToVerify);
84 | }
85 |
86 | public static String encryptData(String key, String track2Data, String ksn) throws Exception {
87 | byte[] keyByteArray = Hex.decodeHex(key);
88 | byte[] key24byte = new byte[24];
89 |
90 | System.arraycopy(keyByteArray, 0, key24byte, 0, 16);
91 | System.arraycopy(keyByteArray, 0, key24byte, 16, 8);
92 |
93 | Cipher chiper = Cipher.getInstance(TerminalConstants.TRANSFORMATION_WITH_PKCS5_PADDING);
94 | chiper.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key24byte, TerminalConstants.ALGORITHM),
95 | new IvParameterSpec(new byte[8]));
96 |
97 | String hexEncocedData = Hex.encodeHexString(track2Data.getBytes("UTF-8"));
98 | byte[] encVal = chiper.doFinal(Hex.decodeHex(hexEncocedData));
99 | String encryptedValue = Hex.encodeHexString(encVal);
100 | return encryptedValue;
101 | }
102 |
103 | private static JSONObject loadKeyAndKSNData() throws Exception {
104 | return loadData(System.getProperty("user.dir") + KEYS_KSN_DATA_FILE);
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/PinTerminal_ISO_0_Format.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | import java.util.logging.Level;
4 | import java.util.logging.Logger;
5 |
6 | import javax.crypto.Cipher;
7 | import javax.crypto.spec.IvParameterSpec;
8 | import javax.crypto.spec.SecretKeySpec;
9 |
10 | import org.apache.commons.codec.binary.Hex;
11 | import org.json.JSONArray;
12 | import org.json.JSONObject;
13 | import org.springframework.http.ResponseEntity;
14 | import org.springframework.web.client.RestTemplate;
15 |
16 | import aws.sample.paymentcryptography.ServiceConstants;
17 |
18 | public class PinTerminal_ISO_0_Format extends AbstractTerminal {
19 |
20 | protected static String KEYS_KSN_DATA_FILE = "/test-data/sample-pek-ksn-data-iso-0-format.json";
21 |
22 | public static void main(String[] args) throws Exception {
23 | testISOFormat0Block();
24 | }
25 |
26 | public static void testISOFormat0Block() throws Exception {
27 | JSONObject pinData = loadData(System.getProperty("user.dir") + PINS_DATA_FILE);
28 | JSONArray pinDataList = pinData.getJSONArray("pins");
29 |
30 | JSONObject keyKsnData = loadData(System.getProperty("user.dir") + KEYS_KSN_DATA_FILE);
31 | JSONArray keyKsnDList = keyKsnData.getJSONArray("data");
32 |
33 | JSONObject panArqcData = loadData(System.getProperty("user.dir") + ARQC_DATA_FILE);
34 | JSONArray panArqcDList = panArqcData.getJSONArray("arqcData");
35 |
36 | for (int i = 0; i < pinDataList.length(); i++) {
37 | JSONObject panPinOBject = pinDataList.getJSONObject(i);
38 |
39 | try {
40 | Logger.getGlobal().log(Level.INFO,"---------testDUKPTPinValidation ---------");
41 | String pan = (panPinOBject).getString("pan");
42 | String pin = (panPinOBject).getString("pin");
43 |
44 | String encodedPin = encodeForISO0Format(pin, pan);
45 |
46 | String dukptVariantKey = keyKsnDList.getJSONObject(i).getString("pek");
47 | String ksn = keyKsnDList.getJSONObject(i).getString("ksn");
48 | String dukptEncryptedBlock = encryptPINWithDukpt(dukptVariantKey, encodedPin);
49 |
50 | String arqcKey = panArqcDList.getJSONObject(i).getString("udk");
51 | String arqcTransactionData = panArqcDList.getJSONObject(i).getString("transactionData");
52 | String arqcCryptogram = Utils.generateIso9797Alg3Mac(arqcKey, arqcTransactionData);
53 |
54 | Logger.getGlobal().log(Level.INFO, "PAN -> {0}, PIN {1}, key {2}, ksn {3}, ARQC {4}", new Object[] {pan,pin,dukptVariantKey,ksn,arqcCryptogram});
55 |
56 | RestTemplate restTemplate = new RestTemplate();
57 |
58 | String verifyPinUrl = ServiceConstants.HOST
59 | + ServiceConstants.PIN_PROCESSOR_SERVICE_ISO_0_FORMAT_PIN_VERIFY_API;
60 |
61 | // Making GET calls for simplicity. In produciton scenarios these would typically be POST calls with appropriate payload.
62 | String finalVerifyPinlUrl = new StringBuilder(verifyPinUrl)
63 | .append("?encryptedPin=")
64 | .append(dukptEncryptedBlock)
65 | .append("&transactionData=")
66 | .append(arqcTransactionData)
67 | .append("&arqcCryptogram=")
68 | .append(arqcCryptogram)
69 | .append("&pan=")
70 | .append(pan)
71 | .append("&ksn=")
72 | .append(ksn)
73 | .toString();
74 |
75 | ResponseEntity verifyPinResponse = restTemplate.getForEntity(finalVerifyPinlUrl,
76 | String.class);
77 | Logger.getGlobal().log(Level.INFO,"Response from PinTranslate service for (DUKPT encrypted) pin verify operation is {0} "
78 | , verifyPinResponse.getBody());
79 | // Adding sleep so there's time between each request - making it easy to look at requsts on the console
80 | Thread.sleep(sleepTimeInMs);
81 | } catch (Exception e) {
82 | e.printStackTrace();
83 | }
84 | }
85 | }
86 |
87 | public static String encryptPINWithDukpt(String dukpt, String encodedPinBlock) throws Exception {
88 | byte[] maskedKey = xorBytes(Hex.decodeHex(dukpt), Hex.decodeHex(TerminalConstants.PIN_MASK));
89 |
90 | byte[] key = new byte[24];
91 |
92 | System.arraycopy(maskedKey, 0, key, 0, 16);
93 | System.arraycopy(maskedKey, 0, key, 16, 8);
94 |
95 | Cipher cipher = Cipher.getInstance(TerminalConstants.TRANSFORMATION);
96 | cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, TerminalConstants.ALGORITHM),
97 | new IvParameterSpec(new byte[8]));
98 |
99 | byte[] encVal = cipher.doFinal(Hex.decodeHex(encodedPinBlock));
100 | String encryptedValue = Hex.encodeHexString(encVal);
101 | return encryptedValue;
102 | }
103 |
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/TerminalConstants.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | import aws.sample.paymentcryptography.CommonConstants;
4 |
5 | public interface TerminalConstants extends CommonConstants{
6 |
7 | public final String PIN_MASK = "00000000000000FF00000000000000FF";
8 |
9 | /* Clear text key */
10 | public final String MAC_KEY_PLAIN_TEXT = "75BDAEF54587CAE6563A5CE57B4B9F9F";
11 |
12 | // Clear text PEK key.
13 | public final String PEK = "545e2aadfd5ec42f2f5be5e3adc75e9b290252a1a219b380";
14 |
15 | public final String ALGORITHM = "DESede"; // Same as TripleDES
16 | public final String NO_PADDING = "NoPadding";
17 | public final String PKCS_PADDING = "PKCS5Padding";
18 | public final String TRANSFORMATION = ALGORITHM + "/" + MODE + "/" + NO_PADDING;
19 |
20 | public final String TRANSFORMATION_WITH_PKCS5_PADDING = ALGORITHM + "/" + MODE + "/" + PKCS_PADDING;
21 | }
22 |
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/TerminalMAC.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | public class TerminalMAC {
4 |
5 | public static String getMac(String data) throws Exception {
6 | return Utils.generateIso9797Alg3Mac(TerminalConstants.MAC_KEY_PLAIN_TEXT, data);
7 | }
8 |
9 | }
--------------------------------------------------------------------------------
/java_sdk_example/src/main/java/aws/sample/paymentcryptography/terminal/Utils.java:
--------------------------------------------------------------------------------
1 | package aws.sample.paymentcryptography.terminal;
2 |
3 | import org.apache.commons.codec.DecoderException;
4 | import org.apache.commons.codec.binary.Hex;
5 | import org.bouncycastle.crypto.BlockCipher;
6 | import org.bouncycastle.crypto.Mac;
7 | import org.bouncycastle.crypto.engines.DESEngine;
8 | import org.bouncycastle.crypto.macs.ISO9797Alg3Mac;
9 | import org.bouncycastle.crypto.params.KeyParameter;
10 |
11 | public class Utils {
12 |
13 | /* public static byte[] generateIso9797Alg3Mac(KeyParameter key, byte[] data) {
14 | final BlockCipher cipher = new DESEngine();
15 | final Mac mac = new ISO9797Alg3Mac(cipher);
16 | mac.init(key);
17 | mac.update(data, 0, data.length);
18 | final byte[] out = new byte[8];
19 | mac.doFinal(out, 0);
20 | return out;
21 | } */
22 |
23 | public static String generateIso9797Alg3Mac(String key, String data) throws DecoderException {
24 | final BlockCipher cipher = new DESEngine();
25 | final Mac mac = new ISO9797Alg3Mac(cipher);
26 |
27 | final KeyParameter keyParameter = new KeyParameter(Hex.decodeHex(key.toCharArray()));
28 | final byte[] dataArray = Hex.decodeHex(data.toCharArray());
29 |
30 | mac.init(keyParameter);
31 | mac.update(dataArray, 0, dataArray.length);
32 | final byte[] out = new byte[8];
33 | mac.doFinal(out, 0);
34 | return Hex.encodeHexString(out);
35 | }
36 |
37 | /* public static String getMac(String data) throws Exception {
38 | final KeyParameter keyParameter = new KeyParameter(Hex.decodeHex(TerminalConstants.MAC_KEY_PLAIN_TEXT.toCharArray()));
39 | final byte[] dataToMac = Hex.decodeHex(data.toCharArray());
40 | final byte[] genMac = Utils.generateIso9797Alg3Mac(keyParameter, dataToMac);
41 | return Hex.encodeHexString(genMac);
42 | } */
43 | }
44 |
--------------------------------------------------------------------------------
/java_sdk_example/src/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootLogger=WARN, A1
2 | log4j.appender.A1=org.apache.log4j.ConsoleAppender
3 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout
4 | log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
5 | # Turn on DEBUG logging in com.amazonaws.request to log
6 | # a summary of requests/responses with {AWS} request IDs
7 | log4j.logger.com.amazonaws.request=DEBUG
--------------------------------------------------------------------------------
/java_sdk_example/test-data/sample-key-ksn-data.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": [
3 | {
4 | "ksn": "FFFF9876543210E00001",
5 | "dataKey": "493DE7639B8644BF9DB9C836E89A1E1C"
6 | },
7 | {
8 | "ksn": "FFFF9876543210E00002",
9 | "dataKey": "AC2CA8D339C8C1988BF4ED0487829DF0"
10 | },
11 | {
12 | "ksn": "FFFF9876543210E00003",
13 | "dataKey": "96A2C77DA58987765A7DAA7E9F7ECAE0"
14 | },
15 | {
16 | "ksn": "FFFF9876543210E00004",
17 | "dataKey": "5C04511F033E16AE3E97D564B458D1E9"
18 | },
19 | {
20 | "ksn": "FFFF9876543210E00005",
21 | "dataKey": "C0A00C350CA987DD677DC023923134F9"
22 | },
23 | {
24 | "ksn": "FFFF9876543210E00006",
25 | "dataKey": "6B90637BBA88E6DC7C8589CD5BD06C57"
26 | },
27 | {
28 | "ksn": "FFFF9876543210E00007",
29 | "dataKey": "E5F85F26D05F57B2FC2DC78882B82683"
30 | },
31 | {
32 | "ksn": "FFFF9876543210E00008",
33 | "dataKey": "097800FE5167F9243F952DDD0518E144"
34 | },
35 | {
36 | "ksn": "FFFF9876543210E00009",
37 | "dataKey": "1A486175670780AB97934D68E1EBE78E"
38 | },
39 | {
40 | "ksn": "FFFF9876543210E00010",
41 | "dataKey": "753E883E78A2B381CD8392A62733F94F"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/java_sdk_example/test-data/sample-pan-arqc-key.json:
--------------------------------------------------------------------------------
1 | {
2 | "arqcData": [
3 | {
4 | "pan": "3677481447555329",
5 | "udk": "F289BA5704E534512301B6BF5DDCC4BF",
6 | "transactionData": "00000005230900000000000001240000000000012425121900123456780000000B03212121000000",
7 | "psn": "00"
8 | },
9 | {
10 | "pan": "6173616228083825",
11 | "udk": "941AC74646D05D83B92CB0F40B321501",
12 | "transactionData": "00000015230900000000000001240000000000012425121900123456780000000B03212121000000",
13 | "psn": "00"
14 | },
15 | {
16 | "pan": "8224434991801596",
17 | "udk": "C810C7E394F2BAA22CC1625B51978F07",
18 | "transactionData": "00000006230900000000000001240000000000012425121900123456780000000B03212121000000",
19 | "psn": "00"
20 | },
21 | {
22 | "pan": "9542488620211526",
23 | "udk": "F88FB3734601F4CB1CB564AB9ED3D9C1",
24 | "transactionData": "00000000230900000000000001240000000000012425121900123456780000000B03212121000000",
25 | "psn": "00"
26 | },
27 | {
28 | "pan": "4045407009377345",
29 | "udk": "E62CF4EA2FE0265129D99B61D9914064",
30 | "transactionData": "00000017230900000000000001240000000000012425121900123456780000000B03212121000000",
31 | "psn": "00"
32 | },
33 | {
34 | "pan": "404317323415280",
35 | "udk": "046BD9DC6D615B37199EE9A8D908B9A1",
36 | "transactionData": "00000032230900000000000001240000000000012425121900123456780000000B03212121000000",
37 | "psn": "00"
38 | },
39 | {
40 | "pan": "332279244354253",
41 | "udk": "75946E5257A1ECAB1F022AB923ECCD92",
42 | "transactionData": "00000004230900000000000001240000000000012425121900123456780000000B03212121000000",
43 | "psn": "00"
44 | },
45 | {
46 | "pan": "5431444918364569",
47 | "udk": "834CE3ADE34A313DA849F8FEF8D6F8E0",
48 | "transactionData": "00000000230900000000000001240000000000012425121900123456780000000B03212121000000",
49 | "psn": "00"
50 | },
51 | {
52 | "pan": "5655025706634897",
53 | "udk": "8094B96B9BD5316E626461D358922C10",
54 | "transactionData": "00000000030900000000000001240000000000012425121900123456780000000B03212121000000",
55 | "psn": "00"
56 | },
57 | {
58 | "pan": "3390294339943651",
59 | "udk": "EC585BF276CD7CDAA2151AC8758A5E15",
60 | "transactionData": "00000019100900000000000001240000000000012425121900123456780000000B03212121000000",
61 | "psn": "00"
62 | }
63 | ]
64 | }
--------------------------------------------------------------------------------
/java_sdk_example/test-data/sample-pek-ksn-data-iso-0-format.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": [
3 | {
4 | "ksn": "FFFF9876543210E00001",
5 | "pek": "91E7A69D04B61A30AE4965847D94A2E2"
6 | },
7 | {
8 | "ksn": "FFFF9876543210E00002",
9 | "pek": "BF08CD931A52C412524B848B6F191E98"
10 | },
11 | {
12 | "ksn": "FFFF9876543210E00003",
13 | "pek": "B914B23BE31D5F20A5A6440EA4D465AE"
14 | },
15 | {
16 | "ksn": "FFFF9876543210E00004",
17 | "pek": "064555312B3385650600A6FA92CF1E2C"
18 | },
19 | {
20 | "ksn": "FFFF9876543210E00005",
21 | "pek": "4323F7032A24D08C4C059058C90D058B"
22 | },
23 | {
24 | "ksn": "FFFF9876543210E00006",
25 | "pek": "CE84E8F042C6935F32C5CB783BDD5AC5"
26 | },
27 | {
28 | "ksn": "FFFF9876543210E00007",
29 | "pek": "9A267460B6DCEA6127D299BC58A15376"
30 | },
31 | {
32 | "ksn": "FFFF9876543210E00008",
33 | "pek": "847ED940BAF066D01CBA9D49295D3CDD"
34 | },
35 | {
36 | "ksn": "FFFF9876543210E00009",
37 | "pek": "7F028726656BF34DF68B61331072CB7A"
38 | },
39 | {
40 | "ksn": "FFFF9876543210E00010",
41 | "pek": "6108941D98CC3BF7199570E6504B0FF1"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/java_sdk_example/test-data/sample-pek-ksn-data-iso-4-format.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": [
3 | {
4 | "ksn": "FFFF9876543210E000000001",
5 | "pek": "F04F3D5E1986D0EBFA833466ACF5156A"
6 | },
7 | {
8 | "ksn": "FFFF9876543210E000000002",
9 | "pek": "6B721A457320BED82F35B49EACFE2EB3"
10 | },
11 | {
12 | "ksn": "FFFF9876543210E000000003",
13 | "pek": "11950B112C04D662B32C62EB385CA982"
14 | },
15 | {
16 | "ksn": "FFFF9876543210E000000004",
17 | "pek": "9B89A9A73D01F33B26C47B739AE91204"
18 | },
19 | {
20 | "ksn": "FFFF9876543210E000000005",
21 | "pek": "58F22F8689F04A029B1F615B1CDDDAA0"
22 | },
23 | {
24 | "ksn": "FFFF9876543210E000000006",
25 | "pek": "481B10671C3503CB300045CBF23F7FF7"
26 | },
27 | {
28 | "ksn": "FFFF9876543210E000000007",
29 | "pek": "764F2BB9B0CBFAD2E2471AF4A9D9B986"
30 | },
31 | {
32 | "ksn": "FFFF9876543210E000000008",
33 | "pek": "E958D700C02410A20B12B4626FCA3706"
34 | },
35 | {
36 | "ksn": "FFFF9876543210E000000009",
37 | "pek": "0F136615D2845361E7E9F0CB83B5F53D"
38 | },
39 | {
40 | "ksn": "FFFF9876543210E000000010",
41 | "pek": "BCA0AA859CD8825B3BDC669AD53F9AF6"
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/java_sdk_example/test-data/sample-pin-pan.json:
--------------------------------------------------------------------------------
1 | {
2 | "pins": [
3 | {
4 | "pan": "3677481447555329",
5 | "pin": "5761"
6 | },
7 | {
8 | "pan": "6173616228083825",
9 | "pin": "8069"
10 | },
11 | {
12 | "pan": "8224434991801596",
13 | "pin": "9723"
14 | },
15 | {
16 | "pan": "9542488620211526",
17 | "pin": "1117"
18 | },
19 | {
20 | "pan": "4045407009377345",
21 | "pin": "9341"
22 | },
23 | {
24 | "pan": "404317323415280",
25 | "pin": "3129"
26 | },
27 | {
28 | "pan": "332279244354253",
29 | "pin": "9243"
30 | },
31 | {
32 | "pan": "5431444918364569",
33 | "pin": "1887"
34 | },
35 | {
36 | "pan": "5655025706634897",
37 | "pin": "8444"
38 | },
39 | {
40 | "pan": "3390294339943651",
41 | "pin": "3083"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/key-import-export/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/amazonlinux/amazonlinux:2023
2 |
3 | RUN mkdir /app && \
4 | curl -LO https://bootstrap.pypa.io/get-pip.py && \
5 | python3 get-pip.py && \
6 | python3 -m pip install psec boto3 pycryptodome
7 |
8 | COPY tr34/import_app/ /app
9 | WORKDIR /app
10 |
11 | ENTRYPOINT ["python3", "apc_demo_keysetup.py"]
--------------------------------------------------------------------------------
/key-import-export/key_exchange/README.md:
--------------------------------------------------------------------------------
1 | # Import Export Migration Scripts
2 | **Note**: Currently supports payShield and Futurex HSMs
3 |
4 | ## Assumptions
5 | KDH : Key Distribution Host
6 | KRD : Key Receiving Device
7 | Futurex : HSM is configured using PMK
8 |
9 | ## Key Exchange using TR34
10 |
11 | The script will establish a KEK (Key Encryption Key) between the chosen KDH and KRD. A set of options are supported for KDH and KRD type.
12 | Update the input_config.json file with details on the host connection.
13 | If KDH or KRD is AWS Payment Cryptography, update the region and/or endpoint you would like to connect to.
14 | If KDH or KRD is Futurex or payShield HSM, update the host ip address and port that you would like to stablish the connection to.
15 |
16 | As part of the key exchange, if you would like to generate a new symmetric KEK, leave 'transport_key' and 'transport_key_kcv' in the config file for KDH as blank.
17 | If you already have a key created, update the key and kcv in the config file for KDH.
18 |
19 | ### Usage
20 |
21 | * Establish the connection to your chosen Payment HSM and update input config file with host and port info to connect.
22 | * Set AWS credentials for the account you want to use for the service resources. Set the region you want to execute the scripts in input config.
23 |
24 | ```
25 | python3 import_export_tr34.py --kdh --krd
26 | ```
27 |
28 | ## Key Exchange using TR31
29 | The script will exchange working keys between KDH and KRD once a KEK is established between KDH and KRD.
30 | Establish a KEK using the Tr34 script and update the kek in the input_config file for both KDH and KRD.
31 |
32 | As part of the key exchange, if you would like to generate a new symmetric KEK, leave 'transport_key' and 'transport_key_kcv' in the config file for KDH as blank.
33 | If you already have a key created, update the key and kcv in the config file for KDH.
34 |
35 | ### Usage
36 |
37 | * Establish the connection to your chosen Payment HSM and update input config file with host and port info to connect.
38 | * Set AWS credentials for the account you want to use for the service resources. Set the region you want to execute the scripts in input config.
39 |
40 | ```
41 | python3 import_export_tr31.py --kdh --krd
42 | ```
43 |
44 | ### Key Exchange using ECDH
45 | The script will perform key agreement using ECDH between KDH and KRD, derive a shared key which will be the KEK to wrap the transport key.
46 | Using this path, you can import/export upto AES-256 keys.
47 |
48 | ### Usage
49 |
50 | ```
51 | python3 import_export_ecdh.py --kdh --krd
52 | ```
53 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/key-import-export/key_exchange/__init__.py
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/assets/atalla-apc-tr34-key-exchange-sequence-diagram - Key Exchange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/key-import-export/key_exchange/hsm/atalla/assets/atalla-apc-tr34-key-exchange-sequence-diagram - Key Exchange.png
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/atalla_to_apc_tr31.py:
--------------------------------------------------------------------------------
1 | import helpers.atalla_helper as atalla_helper
2 | import helpers.apc_helper as apc_helper
3 | import argparse
4 |
5 | """
6 | Usage - python3 atalla_to_apc_tr31.py --host localhost --port 7000
7 | -wrappingKey "1kDNE000,14FB26DD179D6AD587FA0181E599F6CC07F0C8D2AAA2334D,BB6AE577B37A1CD7"
8 | --wrappedKey "1CDNE000,55343DEFA0898223CCCDD33AAAFFFF2342B09234ABCDEF54,CDA43234FFFED091"
9 | --apcWrappingKeyARN "arn:aws:payment-cryptography:us-west-2:111222333444:key/rd56grgskugzelkz
10 | """
11 | def export_tr31(working_key,wrapping_key,atalla_address):
12 |
13 | print("Exporting working key using command 11A using TR-31 key block version B. This assumes the working key is not a MAC key. If MAC key, use either of values M0,M1,M3. Refer to Atalla doc for command 11A")
14 | print("The exportability of the key is is copied from byte 4 of AKB header of the working key")
15 | data = '<11A#B###' + wrapping_key + '#' + working_key + '#' + '00#0##>'
16 | response = atalla_helper.send_data(atalla_address, data.encode(), '<21A#(.*?)#(.*?)#>', b'>') #<220#Public Key#Private Key#Check Digits#[Key slot#]>
17 | tr31WorkingKey = response[0]
18 | tr31WorkingKeyKCV = response[1]
19 | return tr31WorkingKey,tr31WorkingKeyKCV
20 |
21 |
22 | if __name__ == "__main__":
23 |
24 | print('###############################################################################')
25 | print ("Sample code to export a working key from Atalla HSM and import into AWS Payment Cryptography using TR-31")
26 | print ("This is currently intended for 3DES keys.")
27 | print ("This code is sample only and comes with no warranty")
28 | print('###############################################################################')
29 |
30 | parser = argparse.ArgumentParser(prog='TR-31 Atalla/Uimaco Export following AWS Payment Cryptography Key Import Concepts',
31 | description='Sample code to generate a TR-31 format working key from Utimaco AT1000 and import into AWS Payment Cryptography',
32 | epilog='This is intended as sample code and comes with no waranty and is not intended for us with production keys.')
33 | parser.add_argument("--wrappingKey", help="Wrapping key that wraps the working key in Atalla", default="")
34 | parser.add_argument("--wrappedKey", help="Wrapped Key to export from Atalla", default="")
35 | parser.add_argument("--apcWrappingKeyARN", help="Wrapping key (KEK) in APC - which has exported from Atalla to APC. This should be the same key from the wrappingKey arg", default="")
36 | parser.add_argument("--host", help="Atalla Host", default="localhost")
37 | parser.add_argument("--port", help="Atalla Port", default=7000,type=int)
38 |
39 | args = parser.parse_args()
40 |
41 | tr31WorkingKey, tr31WorkingKeyKCV = export_tr31(args.wrappedKey, args.wrappingKey, (args.host, args.port))
42 | print("TR-31 Working Key: ", tr31WorkingKey)
43 | print("TR-31 Working Key KCV: ", tr31WorkingKeyKCV)
44 | importedKeyARN,importedKeyKCV= apc_helper.importTR31Payload(tr31WorkingKey,args.apcWrappingKeyARN)
45 | print("Imported Key ARN: ", importedKeyARN)
46 | print("Imported Key KCV: ", importedKeyKCV)
47 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/helpers/apc_helper.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import base64
3 | from botocore.exceptions import ClientError
4 | from cryptography.hazmat.primitives import serialization
5 | from cryptography import x509
6 | import logging
7 |
8 | apc_client = boto3.client('payment-cryptography')
9 | publicKeyCertificateAliasName = 'alias/tr34-key-import-kdh-ca'
10 | #un-comment to see debug logs for AWS SDK
11 | #boto3.set_stream_logger('', logging.DEBUG)
12 | #boto3.set_stream_logger('boto3.resources', logging.INFO)
13 |
14 | def verify_certificate_state(key_arn):
15 | """
16 | Verifies the state of the imported certificate
17 |
18 | Args:
19 | key_arn (str): ARN of the imported key
20 |
21 | Returns:
22 | bool: True if certificate is enabled and complete, False otherwise
23 | """
24 | try:
25 | response = apc_client.describe_key(
26 | KeyIdentifier=key_arn
27 | )
28 |
29 | key_state = response['Key']['KeyState']
30 | is_enabled = response['Key']['Enabled']
31 |
32 | if key_state == 'CREATE_COMPLETE' and is_enabled:
33 | print(f"Certificate status: {key_state}, Enabled: {is_enabled}")
34 | return True
35 | else:
36 | print(f"Certificate not ready. Status: {key_state}, Enabled: {is_enabled}")
37 | return False
38 |
39 | except ClientError as e:
40 | print(f"Error verifying certificate state: {e}")
41 | return False
42 |
43 | def importPublicCACertificate(kdhPublicKeyCertificate):
44 | cert_data = kdhPublicKeyCertificate.encode('ascii')
45 | """ with open(publicKeyCertificatePath, 'rb') as cert_file:
46 | cert_data = cert_file.read() """
47 | # Load the certificate
48 | cert = x509.load_pem_x509_certificate(cert_data)
49 |
50 | #keyAlias = apc_client.get_alias(AliasName=publicKeyCertificateAliasName)
51 |
52 | kdh_ca_key_arn = apc_client.import_key(Enabled=True, KeyMaterial={
53 | 'RootCertificatePublicKey': {
54 | 'KeyAttributes': {
55 | 'KeyAlgorithm': 'RSA_2048',
56 | 'KeyClass': 'PUBLIC_KEY',
57 | 'KeyModesOfUse': {
58 | 'Verify': True,
59 | },
60 | 'KeyUsage': 'TR31_S0_ASYMMETRIC_KEY_FOR_DIGITAL_SIGNATURE',
61 | },
62 | 'PublicKeyCertificate': base64.b64encode(cert.public_bytes(encoding=serialization.Encoding.PEM)).decode('UTF-8')
63 | }
64 | }, KeyCheckValueAlgorithm='ANSI_X9_24', Tags=[])['Key']['KeyArn']
65 |
66 | tagResponse = apc_client.tag_resource(
67 | ResourceArn= kdh_ca_key_arn,
68 | Tags=[
69 | {
70 | 'Key': 'project',
71 | 'Value': 'sample-atalla-tr34-exchange'
72 | },
73 | ]
74 | )
75 | """ aliasResponse = apc_client.create_alias(
76 | AliasName=publicKeyCertificateAliasName,
77 | KeyArn=kdh_ca_key_arn
78 | )
79 | print(f"aliasResponse: {aliasResponse}")
80 | """
81 | print(f"Imported KDH CA certificate into APC with key ARN: {kdh_ca_key_arn}")
82 | return kdh_ca_key_arn
83 |
84 | def getKeyIfExixts(alias_name):
85 | try:
86 | keyAlias = apc_client.get_alias(AliasName=alias_name)
87 |
88 | # Check if response has the expected structure
89 | if (keyAlias and
90 | isinstance(keyAlias, dict) and
91 | 'Alias' in keyAlias and
92 | isinstance(keyAlias['Alias'], dict) and
93 | 'AliasName' in keyAlias['Alias']):
94 |
95 | return keyAlias['Alias']['AliasName']
96 | return None
97 |
98 | except apc_client.exceptions.NotFoundException:
99 | return None
100 |
101 | def deleteKeyIfExixts(key_arn):
102 | try:
103 | payment_crypto = boto3.client('payment-cryptography')
104 |
105 | # Schedule the key for deletion
106 | response = payment_crypto.delete_key(
107 | KeyIdentifier=key_arn,
108 | DeleteKeyInDays=3
109 | )
110 |
111 | print(f"Key {key_arn} scheduled for deletion in 3 days")
112 | return True
113 |
114 | except payment_crypto.exceptions.NotFoundException:
115 | print(f"Key {key_arn} not found")
116 | return False
117 | except payment_crypto.exceptions.InvalidStateException:
118 | print(f"Key {key_arn} is in an invalid state for deletion")
119 | return False
120 | except Exception as e:
121 | print(f"Error deleting key: {str(e)}")
122 | return False
123 |
124 | def importTR34Payload(tr34Payload,nonce,kdh_ca_key_arn,kdh_ca_certificate,importToken):
125 | print("Importing TR34 payload into APC ...")
126 | # Load the certificate
127 | cert = x509.load_pem_x509_certificate(kdh_ca_certificate.encode('ascii'))
128 |
129 | trt34_import_res = apc_client.import_key(
130 | Enabled=True,
131 | KeyMaterial={
132 | "Tr34KeyBlock": {
133 | 'CertificateAuthorityPublicKeyIdentifier': kdh_ca_key_arn,
134 | 'ImportToken': importToken,
135 | 'KeyBlockFormat': 'X9_TR34_2012',
136 | 'SigningKeyCertificate': base64.b64encode(cert.public_bytes(encoding=serialization.Encoding.PEM)).decode('UTF-8'),
137 | 'WrappedKeyBlock': tr34Payload,
138 | 'RandomNonce': nonce,
139 | }
140 | },
141 | KeyCheckValueAlgorithm='ANSI_X9_24',
142 | Tags= [{"Key": "Type", "Value" : "Atalla-Test"}]
143 | )
144 | KeyArn=trt34_import_res['Key']['KeyArn']
145 | print(f"Imported TR34 payload with key ARN: {KeyArn}")
146 |
147 | def importTR31Payload(tr31_payload,wrappingKeyARN):
148 | print("Importing TR31 payload into ", tr31_payload, "APC with wrapping key ", wrappingKeyARN)
149 | keyMaterial={
150 | "Tr31KeyBlock": {
151 | 'WrappedKeyBlock': tr31_payload.upper(),
152 | 'WrappingKeyIdentifier': wrappingKeyARN
153 | }
154 | }
155 |
156 | try:
157 | imported_symmetric_key_res = apc_client.import_key(
158 | Enabled=True,
159 | KeyMaterial=keyMaterial)
160 | return imported_symmetric_key_res["Key"]["KeyArn"],imported_symmetric_key_res["Key"]["KeyCheckValue"]
161 | except Exception as e:
162 | # Capture error information
163 | output = "failed: " + str(e)
164 | detail = traceback.format_exc()
165 | print(output+" "+ detail)
166 | raise Exception("Error")
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/helpers/atalla_helper.py:
--------------------------------------------------------------------------------
1 | import re
2 | import time
3 | import socket
4 | import binascii
5 | from typing import Tuple
6 |
7 | def createRsaKey(atalla_address):
8 | print("Generating RSA key pair from Atalla using command 120")
9 | #generate rsa key pair
10 | data = '<120#w#010001#2048#>'
11 | response = send_data(atalla_address, data.encode(), '<220#(.*?)#(.*?)#(.*?)#>', b'>') #<220#Public Key#Private Key#Check Digits#[Key slot#]>
12 | pubKey = response[0]
13 | privKey = response[1]
14 | return pubKey,privKey
15 |
16 | def sign139(keyReference, message, atalla_address ):
17 | print("Signing with Atalla using command 139")
18 | #valjue of 5 means sha-256
19 | data = '<139#5#1#%s######%s#>' % (binascii.hexlify(message).decode().upper(), keyReference)
20 | response = send_data(atalla_address, data.encode(), '<239#.*?#(.*?)#.*>', b'>')
21 | return response[0]
22 |
23 | def send_data(target: Tuple[str, int], data: bytes, pattern: str, terminator: bytes = b''):
24 |
25 | s = socket.create_connection(target, timeout=10)
26 | s.settimeout(30)
27 | s.sendall(data)
28 |
29 | end_time = time.time() + 30
30 |
31 | output = b''
32 | while end_time > time.time():
33 | try:
34 | output += s.recv(2**16)
35 | if terminator and terminator in output:
36 | break
37 | except socket.timeout:
38 | break
39 |
40 | matcher = re.match(pattern, output.decode())
41 | if not matcher:
42 | raise Exception('Failed to parse data ' + output.decode() + ' for command ' + data.decode())
43 | return matcher.groups()
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/helpers/aws_private_ca_helper.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import time
3 | from botocore.exceptions import ClientError
4 |
5 | TAG_KEY = "APC_TR34"
6 | controlplane_client = boto3.client("payment-cryptography")
7 | private_ca = boto3.client("acm-pca")
8 |
9 | def create_certificate_authority():
10 | # create Certificate Authority for short-lived certificates
11 | ca_arn = private_ca.create_certificate_authority(
12 | CertificateAuthorityConfiguration={
13 | 'KeyAlgorithm': 'RSA_2048',
14 | 'SigningAlgorithm': 'SHA256WITHRSA',
15 | 'Subject': {
16 | 'Country': 'US',
17 | 'Organization': 'AWS Samples',
18 | 'OrganizationalUnit': 'APC',
19 | 'State': 'CA',
20 | 'CommonName': 'AWS Samples',
21 | }
22 | },
23 | CertificateAuthorityType='ROOT',
24 | UsageMode='SHORT_LIVED_CERTIFICATE'
25 | )['CertificateAuthorityArn']
26 |
27 | state = "CREATING"
28 | while state == "CREATING":
29 | time.sleep(1)
30 | state = private_ca.describe_certificate_authority(CertificateAuthorityArn=ca_arn)['CertificateAuthority'][
31 | 'Status']
32 | print(state)
33 | return ca_arn
34 |
35 | def create_private_ca():
36 | print("Creating AWS Private CA")
37 | cert_authority_arn = create_certificate_authority()
38 | print("Newly created Private CA ARN: %s" % cert_authority_arn)
39 | # add tag to CA
40 | private_ca.tag_certificate_authority(
41 | CertificateAuthorityArn=cert_authority_arn,
42 | Tags=[
43 | {
44 | 'Key': TAG_KEY,
45 | 'Value': 'Sample'
46 | },
47 | ]
48 | )
49 | print("Getting root CA CSR")
50 | csr = private_ca.get_certificate_authority_csr(CertificateAuthorityArn=cert_authority_arn)['Csr']
51 | print("self-signing root CA CSR")
52 | certificate, chain = sign_with_private_ca(cert_authority_arn, csr, {
53 | 'Value': 10,
54 | 'Type': 'YEARS'
55 | }, template='arn:aws:acm-pca:::template/RootCACertificate/V1')
56 | print("Importing signed certificate as ROOT")
57 | private_ca.import_certificate_authority_certificate(CertificateAuthorityArn=cert_authority_arn,
58 | Certificate=certificate)
59 | print("CA Setup complete")
60 | return cert_authority_arn
61 |
62 | def find_or_create_private_ca():
63 | # find existing CA
64 | for ca in private_ca.list_certificate_authorities()['CertificateAuthorities']:
65 | if ca['Status'] == 'ACTIVE':
66 | # get ca tags
67 | tags = private_ca.list_tags(CertificateAuthorityArn=ca['Arn'])
68 | for tag in tags['Tags']:
69 | if tag['Key'] == TAG_KEY:
70 | # if tag is present, use this CA
71 | return ca['Arn']
72 | return create_private_ca()
73 |
74 | def issue_certificate(csr_content):
75 | """
76 | Issues a certificate using AWS Private CA
77 |
78 | Args:
79 | ca_arn (str): ARN of the private CA
80 | csr_file_path (str): Path to the CSR file
81 |
82 | Returns:
83 | str: Certificate ARN if successful, None otherwise
84 | """
85 | try:
86 | # Create ACM PCA client
87 | acmpca_client = boto3.client('acm-pca')
88 | ca_arn = find_or_create_private_ca()
89 | # Request to issue certificate
90 | response = acmpca_client.issue_certificate(
91 | CertificateAuthorityArn=ca_arn,
92 | Csr=csr_content,
93 | SigningAlgorithm='SHA256WITHRSA',
94 | Validity={
95 | 'Value': 7,
96 | 'Type': 'DAYS'
97 | },
98 | TemplateArn='arn:aws:acm-pca:::template/EndEntityCertificate/V1'
99 | )
100 |
101 | certificate_arn = response['CertificateArn']
102 |
103 | # Wait for certificate to be issued
104 | waiter = acmpca_client.get_waiter('certificate_issued')
105 | waiter.wait(
106 | CertificateAuthorityArn=ca_arn,
107 | CertificateArn=certificate_arn
108 | )
109 |
110 | # Get the issued certificate
111 | response = acmpca_client.get_certificate(
112 | CertificateAuthorityArn=ca_arn,
113 | CertificateArn=certificate_arn
114 | )
115 |
116 | certificate = response['Certificate']
117 | certificate_chain = response['CertificateChain']
118 |
119 | """ # Save the certificate and chain to files
120 | with open('certificate.pem', 'w') as f:
121 | f.write(certificate)
122 |
123 | with open('certificate_chain.pem', 'w') as f:
124 | f.write(certificate_chain) """
125 |
126 | return certificate, certificate_chain
127 |
128 | except ClientError as e:
129 | print(f"Error issuing certificate: {e}")
130 | return None
131 |
132 | def sign_with_private_ca(ca_arn, csr, validity, template="arn:aws:acm-pca:::template/EndEntityCertificate/V1"):
133 | """
134 | Signs the client-side Key with AWS Private CA and returns the Certificate and Certificate Chain
135 | :param validity:
136 | :param ca_arn:
137 | :param csr: Certificate Signing Request
138 | :param template: Template ARN to use for the certificate
139 | :return:
140 | """
141 | client = boto3.client('acm-pca')
142 | response = client.issue_certificate(
143 | CertificateAuthorityArn=ca_arn,
144 | Csr=csr,
145 | TemplateArn=template,
146 | SigningAlgorithm='SHA256WITHRSA',
147 | Validity=validity
148 | )
149 | certificate_arn = response['CertificateArn']
150 | time.sleep(0.5)
151 |
152 | while 1:
153 | try:
154 | certificate_response = client.get_certificate(CertificateArn=certificate_arn,
155 | CertificateAuthorityArn=ca_arn)
156 | if 'CertificateChain' in certificate_response:
157 | chain = certificate_response['CertificateChain']
158 | else:
159 | chain = None
160 | return certificate_response['Certificate'], chain
161 | except client.exceptions.RequestInProgressException:
162 | time.sleep(0.1)
163 |
164 | """ def setup():
165 | ca_arn = find_or_create_private_ca()
166 | #ca_certificate = private_ca.get_certificate_authority_certificate(CertificateAuthorityArn=ca_arn)['Certificate']
167 | print("CA Certificate: %s" % ca_arn)
168 | return ca_arn, ca_arn
169 |
170 | setup() """
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/helpers/csr_helper.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals, division, absolute_import, print_function
3 |
4 | import binascii
5 | import sys
6 |
7 | import helpers.atalla_helper as atalla_helper
8 | from asn1crypto import x509, keys, csr, pem, algos
9 | from cryptography.hazmat.backends import default_backend
10 | from cryptography.hazmat.primitives.serialization import load_der_public_key
11 |
12 | def _writer(func):
13 | name = func.__name__
14 | return property(fget=lambda self: getattr(self, '_%s' % name), fset=func)
15 |
16 |
17 | def pem_armor_csr(certification_request):
18 | """
19 | Encodes a CSR into PEM format
20 |
21 | :param certification_request:
22 | An asn1crypto.csr.CertificationRequest object of the CSR to armor.
23 | Typically this is obtained from CSRBuilder.build().
24 |
25 | :return:
26 | A byte string of the PEM-encoded CSR
27 | """
28 |
29 | return pem.armor(
30 | 'CERTIFICATE REQUEST',
31 | certification_request.dump()
32 | )
33 |
34 | class AtallaCSRBuilder(object):
35 |
36 | _subject = None
37 | _hash_algo = None
38 | _basic_constraints = None
39 | _subject_alt_name = None
40 | _key_usage = None
41 | _extended_key_usage = None
42 | _other_extensions = None
43 |
44 | _special_extensions = set([
45 | 'basic_constraints',
46 | 'subject_alt_name',
47 | 'key_usage',
48 | 'extended_key_usage',
49 | ])
50 |
51 | def __init__(self, subject):
52 | """
53 | Unless changed, CSRs will use SHA-256 for the signature
54 |
55 | :param subject:
56 | An asn1crypto.x509.Name object, or a dict - see the docstring
57 | for .subject for a list of valid options
58 |
59 | :param kms_arn:
60 | KMS Key Pair ARN with key usage SIGN_VERIFY
61 | """
62 |
63 | self.subject = subject
64 | self.ca = False
65 |
66 | self._hash_algo = 'sha256'
67 | self._other_extensions = {}
68 |
69 | @_writer
70 | def subject(self, value):
71 | is_dict = isinstance(value, dict)
72 | if is_dict:
73 | value = x509.Name.build(value)
74 |
75 | self._subject = value
76 |
77 | @property
78 | def ca(self):
79 | """
80 | None or a bool - if the request is for a CA cert. None indicates no
81 | basic constraints extension request.
82 | """
83 |
84 | if self._basic_constraints is None:
85 | return None
86 |
87 | return self._basic_constraints['ca'].native
88 |
89 | @ca.setter
90 | def ca(self, value):
91 | if value is None:
92 | self._basic_constraints = None
93 | return
94 |
95 | self._basic_constraints = x509.BasicConstraints({'ca': bool(value)})
96 |
97 | if value:
98 | self._key_usage = x509.KeyUsage(set(['key_cert_sign', 'crl_sign']))
99 | self._extended_key_usage = x509.ExtKeyUsageSyntax(['ocsp_signing'])
100 | else:
101 | self._key_usage = x509.KeyUsage(set(['digital_signature', 'key_encipherment']))
102 | self._extended_key_usage = x509.ExtKeyUsageSyntax(['server_auth', 'client_auth'])
103 |
104 | def _determine_critical(self, name):
105 | if name == 'subject_alt_name':
106 | return len(self._subject) == 0
107 |
108 | if name == 'basic_constraints':
109 | return self.ca is True
110 |
111 | return {
112 | 'subject_directory_attributes': False,
113 | 'key_usage': True,
114 | 'issuer_alt_name': False,
115 | 'name_constraints': True,
116 | # Based on example EV certificates, non-CA certs have this marked
117 | # as non-critical, most likely because existing browsers don't
118 | # seem to support policies or name constraints
119 | 'certificate_policies': False,
120 | 'policy_mappings': True,
121 | 'policy_constraints': True,
122 | 'extended_key_usage': False,
123 | 'inhibit_any_policy': True,
124 | 'subject_information_access': False,
125 | 'tls_feature': False,
126 | 'ocsp_no_check': False,
127 | }.get(name, False)
128 |
129 | def build_csr(self,priv_key,pub_key,atalla_address):
130 | """
131 | Validates the certificate information, constructs an X.509 certificate
132 | and then signs it
133 |
134 | :return:
135 | An asn1crypto.csr.CertificationRequest object of the request
136 | """
137 | def _make_extension(name, value):
138 | return {
139 | 'extn_id': name,
140 | 'critical': self._determine_critical(name),
141 | 'extn_value': value
142 | }
143 |
144 | extensions = []
145 | for name in sorted(self._special_extensions):
146 | value = getattr(self, '_%s' % name)
147 | if value is not None:
148 | extensions.append(_make_extension(name, value))
149 |
150 | for name in sorted(self._other_extensions.keys()):
151 | extensions.append(_make_extension(name, self._other_extensions[name]))
152 |
153 | attributes = []
154 | if extensions:
155 | attributes.append({
156 | 'type': 'extension_request',
157 | 'values': [extensions]
158 | })
159 |
160 | self._subject_public_key = pub_key.asn1
161 | certification_request_info = csr.CertificationRequestInfo({
162 | 'version': 'v1',
163 | 'subject': self._subject,
164 | 'subject_pk_info': self._subject_public_key,
165 | 'attributes': attributes
166 | })
167 |
168 | signature = atalla_helper.sign139(priv_key,certification_request_info.dump(),atalla_address)
169 | signature_algorithm_id = algos.SignedDigestAlgorithm({
170 | 'algorithm': 'sha256_rsa',
171 | })
172 | csrRequest = csr.CertificationRequest({
173 | 'certification_request_info': certification_request_info,
174 | 'signature_algorithm': signature_algorithm_id,
175 | 'signature': binascii.unhexlify(signature)
176 | })
177 |
178 | csrRequest = pem_armor_csr(csrRequest)
179 |
180 | return csrRequest
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/keys/params_for_import.json:
--------------------------------------------------------------------------------
1 | {
2 | "ImportToken": "import-token-",
3 | "ParametersValidUntilTimestamp": "2025-01-05T14:38:26.899000-08:00",
4 | "WrappingKeyAlgorithm": "RSA_2048",
5 | "WrappingKeyCertificate": "",
6 | "WrappingKeyCertificateChain": ""
7 | }
8 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/keys/tr34_offline_krd_public_key_akb:
--------------------------------------------------------------------------------
1 | 1kREE000,0000000000...........3F81F48BB9632FF7E9530EA7B0444F5F8D13E0E36F4C2991ABD87B6EFE5087858F5FA49B2471BBB,990ABBFF9933DD12
2 |
3 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/readme.md:
--------------------------------------------------------------------------------
1 | # TR-34 Exchange between Atalla -> AWS Payment Cryptography
2 | This script will exchange the Key Exchange Key (KEK) from Atalla into AWS Payment Cryptography, thus bootstrapping it for migration of working keys from Atalla to AWS Payment Cryptography.
3 |
4 | ## Assumptions
5 | 1. This script assumes that you have access to an Atalla HSM and can perform administrative commands such as 12A
6 | 2. The Atalla HSM is configured using a TDES MFK. Slight changes might be needed if you are using an AES MFK
7 | 3. Working keys to be migrated are 2-key or 3-key TDES
8 | 4. The HSM is enabled for the following commands: 12A,120,136,139 for initial exchange and then 11A and option E2 to migrate working keys using TR-31.
9 | 5. You have access to AWS Private CA or access to another CA of your chosing to sign a CSR.
10 |
11 |
12 | ## Prerequisites
13 |
14 | 1. Call [GetParametersForImport](https://docs.aws.amazon.com/payment-cryptography/latest/APIReference/API_GetParametersForImport.html). Save the output under [keys/params_for_import.json](./keys/params_for_import.json)
15 | 2. Import the KRD (in this case AWS Payment Cryptography) Leaf certificate [WrappingKeyCertificate] from [params_for_import.json](./keys/params_for_import.json).json into Atalla.
16 | **_NOTE:_** Typically you would import the CA certificate and then root certificate. However, the command for importing chained certificates (123) does not support the SHA-512 hash used by the AWS Payment Cryptography Service, so you must directly trust the leaf cert using 12A instead which is a manual process requiring dual control.
17 |
18 | Steps -
19 | 1. Get the modulus of the public key cert. Example - ```echo "LS0tLS1CRUdJTiBDRVJUSUZJQ..." | base64 -d | openssl x509 -modulus -noout```
20 | 2. Trust KRD public key on Atalla.
21 | In order to establish trust, use command 12A. This is a 2 step command that requires dual control with the final output being an AKB of the public key.
22 | - Step 1 - ```<12A#1kREE000#010001#modulus##>``` which returns a security challenge. The 12A command needs to be rerun after the security challenge is completed via admin screen on Atalla.
23 | - Step 2 -
24 | ```<12A#1kREE000#010001#modulus#security channelge answer#>```. The output of the second command gives the public key akb and will look something like ```<22A#OK#1kREE000,00030100010100B0357EB48EEEEAE......#>```
25 | - Save output from the header onwards to the end of the response (```1kREE000,00030.....BBB,26A8A315```) on to the [krd_public_key_akb file](./keys/tr34_offline_krd_public_key_akb).
26 |
27 | ## Script Usage
28 | ```
29 | 1. python -m venv .venv
30 | 2. source .venv/bin/activate
31 | 3. pip3 install -r requirements.txt
32 | 4. python3 atalla_to_apc_tr34.py
33 | Example: python3 atalla_to_apc_tr34.py '127.0.0.1' 7000
34 | ```
35 |
36 | ## The atalla_to_apc_tr34.py script does the following -
37 | 1. Generate Signing Key on Atalla (command 120)
38 | 2. Generate CSR within this sample code and then sign on the Atalla (command 139). Update the CSR subject information in file `atalla_to_apc_tr34.py` if desired.
39 | **_NOTE:_** although command 124 is typically used for digital signatures, it cannot be used when the purpose of the key will be to sign TR-34 payloads.
40 | 3. Have CSR signed by CA of your choice ([AWS Private Certificate Authority](https://aws.amazon.com/private-ca/) is used here)
41 | 4. Trust Signing CA on AWS Payment Cryptography - Use [Import Public Root KeyCertificate](https://docs.aws.amazon.com/payment-cryptography/latest/APIReference/API_ImportKey.html)
42 | 5. Build TR-34 payload and generate KEK (136). Save Atalla KEK for future use.
43 | 6. Sign TR-34 payload (139)
44 | 7. Build combined payload using the TR-34 non-CMS payload structure (code sample - not cryptographic)
45 | 8. Import key into AWS Payment Cryptography by calling [ImportKey](https://docs.aws.amazon.com/payment-cryptography/latest/APIReference/API_ImportKey.html)
46 |
47 | ## TR-34 KEK Exchange Flow
48 | 
49 |
50 | ## TR-31 Exchagne flow for working keys
51 |
52 | After KEK exchange, you can now import working keys as below via script `atalla_to_apc_tr31.py` -
53 |
54 | ### Usage
55 | Pass the MFK encrypted wrapping key from step 5 above, MFK encrypted working key and ARN of the wrapping key (KEK) imported into AWS Payment Cryptography from step 8 above
56 | ```
57 | 1. python -m venv .venv
58 | 2. source .venv/bin/activate
59 | 3. python3 atalla_to_apc_tr31.py --host localhost --port 7000
60 | -wrappingKey "1kDNE000,14FB26DD179D6AD587FA0181E599F6CC07F0C8D2AAA2334D,BB6AE577B37A1CD7"
61 | --wrappedKey "1CDNE000,55343DEFA0898223CCCDD33AAAFFFF2342B09234ABCDEF54,CDA43234FFFED091"
62 | --apcWrappingKeyARN "arn:aws:payment-cryptography:us-west-2:111222333444:key/rd56grgskugzelkz
63 |
64 | ```
65 |
66 | **_NOTE:_** If option E2 is not enabled on Atalla, it may restrict your ability to output certain keys (including KEKs). This is a common source of error 0607 (security violation).
67 |
68 | ## AWS Resource Used
69 | 1. This sample uses a Private CA short lived CA. To limit charges, delete the CA after completing testing
70 | 2. This sample creates key(s) on AWS Payment Cryptography. To limit charges, delete key(s) after completing testing
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/atalla/requirements.txt:
--------------------------------------------------------------------------------
1 | asn1crypto
2 | asymmetric
3 | oscrypto
4 | boto3
5 | cryptography
6 | pycryptodome
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/futurex/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/key-import-export/key_exchange/hsm/futurex/__init__.py
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/futurex/sample_hsm_config.txt:
--------------------------------------------------------------------------------
1 | Major Keys:
2 | -----------------
3 | PMK: KCV:****** Type:AES 256
4 | MFK: (Not Loaded)
5 | KEK: (Not Loaded)
6 | BAK: (Not Loaded)
7 | SCEK: (Not Loaded)
8 | FTK: (Not Loaded)
9 |
10 | Enabled Features:
11 | -----------------
12 | RSA: Enabled
13 | ECC: Enabled
14 | FIPS Mode: Disabled
15 | PCI-HSM Mode: Enabled
16 | Key Wrap Policy: CRYPTOGRAM,AKB
17 | PKCS 11: Enabled
18 |
19 | Misc Settings:
20 | -----------------
21 | Key Block Policy: CRYPTOGRAM,TR31,AKB
22 | RSA Blinding: ON
23 | Check Digit Length: 6
24 | Major Key Standard KCV Length: 4
25 | Major Key CMAC KCV Length: 6
26 | RSA Legacy Functionality: ON
27 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/payshield/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/key-import-export/key_exchange/hsm/payshield/__init__.py
--------------------------------------------------------------------------------
/key-import-export/key_exchange/hsm/payshield/sample_hsm_config.txt:
--------------------------------------------------------------------------------
1 | Echo: OFF
2 | User storage key length: SINGLE
3 | Default LMK identifier: 00
4 | Management LMK identifier: 00
5 | Enable settings per LMK: NO
6 | Atalla ZMK variant support: OFF
7 | Enable ZMK translate command: NO
8 | Enable X9.17 for import: NO
9 | Enable X9.17 for export: NO
10 | ZMK length: DOUBLE
11 | Authorized State required when importing a key under an RSA key: YES
12 | Restrict key check values to 6 hex chars: YES
13 | Key export and import in trusted format only: YES
14 | Ensure LMK Identifier in command corresponds with host port: NO
15 | Ignore LMK ID in Key Block Header: NO
16 | Enable import and export of RSA Private keys: NO
17 | Enable import of a ZMK: NO
18 | Enable export of a ZMK: NO
19 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/import_export_ecdh.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa: E402
2 | import argparse
3 | import json
4 | import os
5 | import sys
6 |
7 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8 |
9 | from key_exchange.hsm.futurex.futurex_hsm import FuturexHsm
10 | from key_exchange.hsm.payshield.payshield_hsm import PayshieldHsm
11 | from key_exchange.utils.apc import Apc
12 | from key_exchange.utils.enums import (
13 | AsymmetricKeyUsage,
14 | EccKeyAlgorithm,
15 | KeyDerivationFunction,
16 | KeyDerivationHashAlgorithm,
17 | KeyExchangeType,
18 | SymmetricKeyAlgorithm,
19 | SymmetricKeyUsage,
20 | )
21 |
22 | def _get_command_line_args():
23 | parser = argparse.ArgumentParser()
24 | parser.add_argument(
25 | "--kdh",
26 | help="Key Distribution Host. Options are [futurex, payshield, apc]",
27 | required=True,
28 | choices=["futurex", "payshield", "apc"],
29 | )
30 | parser.add_argument(
31 | "--krd",
32 | help="Key Receiving Device. Options are [apc]",
33 | required=False,
34 | default="apc",
35 | choices=["apc"],
36 | )
37 |
38 | return parser.parse_args()
39 |
40 | def _get_kdh_krd_hosts(kdh, krd, kdh_config, krd_config):
41 | # For KRD, only APC is supported for now
42 | krd_host = Apc(krd_config)
43 |
44 | if "futurex" == kdh:
45 | kdh_host = FuturexHsm(kdh_config)
46 | elif "apc" == kdh:
47 | kdh_host = Apc(kdh_config)
48 | elif "payshield" == kdh:
49 | if kdh_config["variant_lmk"]:
50 | print("ECDH Key exchange is not supported in Payshield with Variant LMK.")
51 | sys.exit(1)
52 | kdh_host = PayshieldHsm(kdh_config)
53 | return kdh_host, krd_host
54 |
55 | def main():
56 | args = _get_command_line_args()
57 | config = dict()
58 | with open(os.path.dirname(__file__) + "/input_config.json", "r") as jsonfile:
59 | config = json.load(jsonfile)
60 |
61 | kdh = args.kdh
62 | krd = args.krd
63 |
64 | print("\n####### Key Exchange using ECDH #######")
65 | print("\nKey Distribution Host (KDH) : ", kdh.upper())
66 | print("Key Receiving Device (KRD) : ", krd.upper())
67 |
68 | kdh_config = config["kdh"][kdh]
69 | krd_config = config["krd"][krd]
70 | kdh_host, krd_host = _get_kdh_krd_hosts(kdh, krd, kdh_config, krd_config)
71 |
72 | key_usage = SymmetricKeyUsage.KBPK
73 | key_algorithm = SymmetricKeyAlgorithm.AES_256
74 | kdh_ca_algorithm = EccKeyAlgorithm.ECC_NIST_P521
75 | kdh_algorithm = EccKeyAlgorithm.ECC_NIST_P521
76 | krd_ca_algorithm = EccKeyAlgorithm.ECC_NIST_P521
77 | krd_algorithm = EccKeyAlgorithm.ECC_NIST_P521
78 |
79 | transport_key = kdh_config["ecdh"]["transport_key"]
80 | transport_key_kcv = kdh_config["ecdh"]["transport_key_kcv"]
81 | if transport_key and transport_key_kcv:
82 | print("\nStep 1 ({}) : Using the transport key from input config".format(kdh.upper()))
83 | print("Transport Key : ", transport_key)
84 | print("KCV : ", transport_key_kcv)
85 | else:
86 | print(
87 | "\nStep 1 ({}) : Generate symmetric transport key with KeyUsage : {} and KeyAlgorithm : {}".format(
88 | kdh.upper(), key_usage.name, key_algorithm.name
89 | )
90 | )
91 | transport_key, transport_key_kcv = kdh_host.create_symmetric_key(key_algorithm, key_usage)
92 | print("Transport Key : ", transport_key)
93 | print("KCV : ", transport_key_kcv)
94 |
95 | print(
96 | "\nStep 2 ({}) : Creating KDH certificate (for key agreement) and certificate chain.".format(
97 | kdh.upper()
98 | )
99 | )
100 | print(
101 | "KDH Certificate KeyAlgorithm : {} , KDH CertificateAuthority KeyAlgorithm : {}".format(
102 | kdh_algorithm.name, kdh_ca_algorithm.name
103 | )
104 | )
105 | kdh_ca_certificate, kdh_private_key, kdh_certificate = kdh_host.generate_certificate_and_chain(
106 | key_algorithm=kdh_algorithm,
107 | ca_key_algorithm=kdh_ca_algorithm,
108 | key_usage=AsymmetricKeyUsage.KEY_AGREEMENT_KEY,
109 | key_exchange_type=KeyExchangeType.ECDH,
110 | )
111 | print("KDH CA Certificate : {}".format(kdh_ca_certificate))
112 | print("KDH Private Key : {}".format(kdh_private_key))
113 | print("KDH Certificate : {}".format(kdh_certificate))
114 |
115 | print(
116 | "\nStep 3 ({}) : Creating KRD certificate (for key agreement) and certificate chain.".format(
117 | krd.upper()
118 | )
119 | )
120 | print(
121 | "KDH Certificate KeyAlgorithm : {} , KDH CertificateAuthority KeyAlgorithm : {}".format(
122 | krd_algorithm.name, krd_algorithm.name
123 | )
124 | )
125 | krd_ca_certificate, krd_private_key, krd_certificate = krd_host.generate_certificate_and_chain(
126 | key_algorithm=krd_algorithm,
127 | ca_key_algorithm=krd_ca_algorithm,
128 | key_usage=AsymmetricKeyUsage.KEY_AGREEMENT_KEY,
129 | key_exchange_type=KeyExchangeType.ECDH,
130 | )
131 | print("KRD CA Certificate : {}".format(krd_ca_certificate))
132 | print("KRD Private Key : {}".format(krd_private_key))
133 | print("KRD Certificate : {}".format(krd_certificate))
134 |
135 | print("\nStep 4 ({}) : Trust KRD certificate chain".format(kdh.upper()))
136 | krd_ca_certificate_trusted = kdh_host.trust_certificate_chain(
137 | krd_ca_certificate, krd_ca_algorithm
138 | )
139 | print("KRD CA Certificate Trusted : {}".format(krd_ca_certificate_trusted))
140 |
141 | print("\nStep 5 ({}) : Trust KDH certificate chain.".format(krd.upper()))
142 | kdh_ca_certificate_trusted = krd_host.trust_certificate_chain(
143 | kdh_ca_certificate, kdh_ca_algorithm
144 | )
145 | print("KDH CA Certificate Trusted : {}".format(kdh_ca_certificate_trusted))
146 |
147 | print(
148 | "\nStep 6 ({}) : Derive KEK (AES_256) using ECDH and export transport key.".format(
149 | kdh.upper()
150 | )
151 | )
152 | derive_key_algorithm = SymmetricKeyAlgorithm.AES_256
153 | key_derivation_function = KeyDerivationFunction.NIST_SP800
154 | hash_algorithm = KeyDerivationHashAlgorithm.SHA_256
155 | shared_info = "0123456789"
156 | exported_key = kdh_host.export_symmetric_key_using_ecdh(
157 | kdh_private_key,
158 | krd_ca_certificate_trusted,
159 | krd_certificate,
160 | derive_key_algorithm,
161 | key_derivation_function,
162 | hash_algorithm,
163 | shared_info,
164 | transport_key,
165 | key_algorithm.name,
166 | )
167 | print("Exported Key : {}".format(exported_key))
168 |
169 | print(
170 | "\nStep 7 ({}) : Derive KEK (AES_256) using ECDH and import transport key.".format(
171 | krd.upper()
172 | )
173 | )
174 | imported_key, imported_key_kcv = krd_host.import_symmetric_key_using_ecdh(
175 | krd_private_key,
176 | kdh_ca_certificate_trusted,
177 | kdh_certificate,
178 | derive_key_algorithm,
179 | key_derivation_function,
180 | hash_algorithm,
181 | shared_info,
182 | exported_key,
183 | )
184 | print("Imported Key : {}".format(imported_key))
185 | print("Imported Key KCV : {}".format(imported_key_kcv))
186 |
187 |
188 | if __name__ == "__main__":
189 | main()
190 |
191 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/import_export_tr31.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa: E402
2 | import argparse
3 | import json
4 | import os
5 | import sys
6 |
7 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8 |
9 | from key_exchange.hsm.futurex.futurex_hsm import FuturexHsm
10 | from key_exchange.hsm.payshield.payshield_hsm import PayshieldHsm
11 | from key_exchange.utils.apc import Apc
12 | from key_exchange.utils.enums import SymmetricKeyAlgorithm, SymmetricKeyUsage
13 |
14 |
15 | def _get_command_line_args():
16 | parser = argparse.ArgumentParser()
17 | parser.add_argument(
18 | "--kdh",
19 | help="Key Distribution Host. Options are [futurex, payshield]",
20 | required=True,
21 | choices=["futurex", "payshield"],
22 | )
23 | parser.add_argument(
24 | "--krd",
25 | help="Key Receiving Device. Options are [apc]",
26 | required=False,
27 | default="apc",
28 | choices=["apc"],
29 | )
30 |
31 | return parser.parse_args()
32 |
33 |
34 | def _get_kdh_krd_hosts(kdh, krd, kdh_config, krd_config):
35 | # For KRD, only APC is supported for now
36 | krd_host = Apc(krd_config)
37 |
38 | if "futurex" == kdh:
39 | kdh_host = FuturexHsm(kdh_config)
40 | elif "payshield" == kdh:
41 | kdh_host = PayshieldHsm(kdh_config)
42 |
43 | return kdh_host, krd_host
44 |
45 |
46 | def main():
47 | args = _get_command_line_args()
48 | config = dict()
49 | with open(os.path.dirname(__file__) + "/input_config.json", "r") as jsonfile:
50 | config = json.load(jsonfile)
51 |
52 | kdh = args.kdh
53 | krd = args.krd
54 |
55 | print("\n####### Key Exchange using TR31 #######")
56 | print("\nKey Distribution Host (KDH) : ", kdh.upper())
57 | print("Key Receiving Device (KRD) : ", krd.upper())
58 |
59 | kdh_config = config["kdh"][kdh]
60 | krd_config = config["krd"][krd]
61 | kdh_host, krd_host = _get_kdh_krd_hosts(kdh, krd, kdh_config, krd_config)
62 |
63 | key_usage = SymmetricKeyUsage.BDK
64 | key_algorithm = SymmetricKeyAlgorithm.TDES_3KEY
65 |
66 | kdh_kek = kdh_config["tr31"]["kek"]
67 | krd_kek = krd_config["tr31"]["kek"]
68 | if not kdh_kek or not krd_kek:
69 | print(
70 | "\nFor import using TR31, a KEK needs to be established between KDH and KRD. Use TR34 to establish the KEK and update input_config file."
71 | )
72 | sys.exit(1)
73 |
74 | transport_key = kdh_config["tr31"]["transport_key"]
75 | transport_key_kcv = kdh_config["tr31"]["transport_key_kcv"]
76 | if transport_key and transport_key_kcv:
77 | print("\nStep 1 ({}) : Using the transport key from input config".format(kdh.upper()))
78 | print("Transport Key : ", transport_key)
79 | print("KCV : ", transport_key_kcv)
80 | else:
81 | print(
82 | "\nStep 1 ({}) : Generate symmetric transport key with KeyUsage : {} and KeyAlgorithm : {}".format(
83 | kdh.upper(), key_usage.name, key_algorithm.name
84 | )
85 | )
86 | transport_key, transport_key_kcv = kdh_host.create_symmetric_key(key_algorithm, key_usage)
87 | print("Transport Key : ", transport_key)
88 | print("KCV : ", transport_key_kcv)
89 |
90 | print("\nStep 2 ({}) : Export the transport key under the KEK using TR31.".format(kdh.upper()))
91 | exported_key = kdh_host.export_symmetric_key_using_tr31(transport_key, kdh_kek, key_algorithm.name)
92 | print("Exported Key using KEK : {}".format(exported_key))
93 |
94 | print("\nStep 3 ({}) : Import the transport key under the KEK using TR31.".format(krd.upper()))
95 | imported_key, imported_key_kcv = krd_host.import_symmetric_key_using_tr31(exported_key, krd_kek)
96 | print("\nImported Key : {}".format(imported_key))
97 | print("Imported Key KCV : {}".format(imported_key_kcv))
98 |
99 |
100 | if __name__ == "__main__":
101 | main()
102 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/import_export_tr34.py:
--------------------------------------------------------------------------------
1 | # flake8: noqa: E402
2 |
3 | import argparse
4 | import json
5 | import os
6 | import re
7 | import sys
8 |
9 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10 |
11 | from key_exchange.hsm.futurex.futurex_hsm import FuturexHsm
12 | from key_exchange.hsm.payshield.payshield_hsm import PayshieldHsm
13 | from key_exchange.utils.apc import Apc
14 | from key_exchange.utils.enums import (
15 | AsymmetricKeyUsage,
16 | KeyExchangeType,
17 | RsaKeyAlgorithm,
18 | SymmetricKeyAlgorithm,
19 | SymmetricKeyUsage,
20 | )
21 |
22 |
23 | def _get_command_line_args():
24 | parser = argparse.ArgumentParser()
25 | parser.add_argument(
26 | "--kdh",
27 | help="Key Distribution Host. Options are [futurex, payshield]",
28 | required=True,
29 | choices=["futurex", "payshield"],
30 | )
31 | parser.add_argument(
32 | "--krd",
33 | help="Key Receiving Device. Options are [apc]",
34 | required=False,
35 | default="apc",
36 | choices=["apc"],
37 | )
38 |
39 | return parser.parse_args()
40 |
41 |
42 | def _get_kdh_krd_hosts(kdh, krd, kdh_config, krd_config):
43 | # For KRD, only APC is supported for now
44 | krd_host = Apc(krd_config)
45 |
46 | if "futurex" == kdh:
47 | kdh_host = FuturexHsm(kdh_config)
48 | elif "payshield" == kdh:
49 | kdh_host = PayshieldHsm(kdh_config)
50 | return kdh_host, krd_host
51 |
52 |
53 | def main():
54 | args = _get_command_line_args()
55 | config = dict()
56 | with open(os.path.dirname(__file__) + "/input_config.json", "r") as jsonfile:
57 | config = json.load(jsonfile)
58 |
59 | kdh = args.kdh
60 | krd = args.krd
61 |
62 | print("\n####### Key Exchange using TR34 #######")
63 | print("\nKey Distribution Host (KDH) : ", kdh.upper())
64 | print("Key Receiving Device (KRD) : ", krd.upper())
65 |
66 | kdh_config = config["kdh"][kdh]
67 | krd_config = config["krd"][krd]
68 | kdh_host, krd_host = _get_kdh_krd_hosts(kdh, krd, kdh_config, krd_config)
69 |
70 | key_usage = SymmetricKeyUsage.KBPK
71 | key_algorithm = SymmetricKeyAlgorithm.TDES_3KEY
72 |
73 | kdh_ca_algorithm = RsaKeyAlgorithm.RSA_2048
74 | kdh_algorithm = RsaKeyAlgorithm.RSA_2048
75 | krd_ca_algorithm = RsaKeyAlgorithm.RSA_4096
76 | krd_algorithm = RsaKeyAlgorithm.RSA_2048
77 |
78 | print(
79 | "\nStep 1 ({}) : Creating KDH certificate (for signing the key block) and certificate chain.".format(
80 | kdh.upper()
81 | )
82 | )
83 | print(
84 | "KDH Certificate KeyAlgorithm : {} , KDH CertificateAuthority KeyAlgorithm : {}".format(
85 | kdh_algorithm.name, kdh_ca_algorithm.name
86 | )
87 | )
88 | kdh_ca_certificate, kdh_private_key, kdh_certificate = kdh_host.generate_certificate_and_chain(
89 | key_algorithm=kdh_algorithm,
90 | ca_key_algorithm=kdh_ca_algorithm,
91 | key_usage=AsymmetricKeyUsage.SIGN,
92 | key_exchange_type=KeyExchangeType.EXPORT_TR34_KEY_BLOCK,
93 | )
94 | print("KDH CA Certificate : {}".format(kdh_ca_certificate))
95 | print("KDH Private Key : {}".format(kdh_private_key))
96 | print("KDH Certificate : {}".format(kdh_certificate))
97 |
98 | print("\nStep 2 ({}) : Get the wrapping public key certificate and chain.".format(krd.upper()))
99 | print(
100 | "KRD Certificate KeyAlgorithm : {} , KRD CertificateAuthority KeyAlgorithm : {}".format(
101 | krd_algorithm.name, krd_ca_algorithm.name
102 | )
103 | )
104 | krd_ca_certificate, krd_private_key, krd_certificate = krd_host.generate_certificate_and_chain(
105 | key_algorithm=krd_algorithm,
106 | ca_key_algorithm=krd_ca_algorithm,
107 | key_usage=AsymmetricKeyUsage.SIGN,
108 | key_exchange_type=KeyExchangeType.IMPORT_TR34_KEY_BLOCK,
109 | )
110 | print("KRD CA Certificate : {}".format(krd_ca_certificate))
111 | print("KRD Private Key : {}".format(krd_private_key))
112 | print("KRD Certificate : {}".format(krd_certificate))
113 |
114 | print("\nStep 3 ({}) : Trust KRD certificate chain".format(kdh.upper()))
115 | krd_ca_certificate_trusted = kdh_host.trust_certificate_chain(
116 | krd_ca_certificate, krd_ca_algorithm
117 | )
118 | print("KRD CA Certificate Trusted : {}".format(krd_ca_certificate_trusted))
119 |
120 | print("\nStep 4 ({}) : Trust KDH certificate chain.".format(krd.upper()))
121 | kdh_ca_certificate_trusted = krd_host.trust_certificate_chain(
122 | kdh_ca_certificate, kdh_ca_algorithm
123 | )
124 | print("KDH CA Certificate Trusted : {}".format(kdh_ca_certificate_trusted))
125 |
126 | transport_key = kdh_config["tr34"]["transport_key"]
127 | transport_key_kcv = kdh_config["tr34"]["transport_key_kcv"]
128 | if transport_key and transport_key_kcv:
129 | print("\nStep 5 ({}) : Using the transport key from input config".format(kdh.upper()))
130 | print("Transport Key : ", transport_key)
131 | print("KCV : ", transport_key_kcv)
132 | else:
133 | print(
134 | "\nStep 5 ({}) : Generate symmetric transport key with KeyUsage : {} and KeyAlgorithm : {}".format(
135 | kdh.upper(), key_usage.name, key_algorithm.name
136 | )
137 | )
138 | transport_key, transport_key_kcv = kdh_host.create_symmetric_key(key_algorithm, key_usage)
139 | print("Transport Key : ", transport_key)
140 | print("KCV : ", transport_key_kcv)
141 |
142 | print("\nStep 6 ({}) : Export the transport key using TR34 (2Pass).".format(kdh.upper()))
143 | tr34_payload, nonce = kdh_host.export_symmetric_key_using_tr34(
144 | kdh_certificate, kdh_private_key, krd_certificate, krd_ca_certificate_trusted, transport_key
145 | )
146 | print("TR34 Payload : {}".format(tr34_payload))
147 | print("Nonce : {}".format(nonce))
148 |
149 | print("\nStep 7 ({}) : Import the transport key using TR34 (2Pass).".format(krd.upper()))
150 | imported_key, imported_key_kcv = krd_host.import_symmetric_key_using_tr34(
151 | krd_certificate,
152 | krd_private_key,
153 | kdh_certificate,
154 | kdh_ca_certificate_trusted,
155 | tr34_payload,
156 | nonce,
157 | )
158 | print("\nImported Key : {}".format(imported_key))
159 | print("Imported Key KCV : {}".format(imported_key_kcv))
160 |
161 |
162 | if __name__ == "__main__":
163 | main()
164 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/input_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "kdh": {
3 | "futurex": {
4 | "host": "127.0.0.1",
5 | "port": 9000,
6 | "tr34": {
7 | "transport_key": "",
8 | "transport_key_kcv": ""
9 | },
10 | "tr31": {
11 | "kek": "",
12 | "transport_key": "",
13 | "transport_key_kcv": ""
14 | },
15 | "ecdh": {
16 | "transport_key": "",
17 | "transport_key_kcv": ""
18 | }
19 | },
20 | "apc": {
21 | "region": "us-west-2",
22 | "ecdh": {
23 | "transport_key": "",
24 | "transport_key_kcv": ""
25 | }
26 | },
27 | "payshield": {
28 | "host": "127.0.0.1",
29 | "port": 9150,
30 | "variant_lmk": true,
31 | "variant_lmk_identifier": "02",
32 | "tr34": {
33 | "transport_key": "",
34 | "transport_key_kcv": ""
35 | },
36 | "tr31": {
37 | "kek": "",
38 | "transport_key": "",
39 | "transport_key_kcv": ""
40 | },
41 | "ecdh": {
42 | "transport_key": "",
43 | "transport_key_kcv": ""
44 | }
45 | }
46 | },
47 | "krd": {
48 | "apc": {
49 | "region": "us-east-2",
50 | "tr31": {
51 | "kek": ""
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/requirements.txt:
--------------------------------------------------------------------------------
1 | cryptography
2 | boto3
3 | pytz
4 |
--------------------------------------------------------------------------------
/key-import-export/key_exchange/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/key-import-export/key_exchange/utils/__init__.py
--------------------------------------------------------------------------------
/key-import-export/key_exchange/utils/enums.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class SymmetricKeyAlgorithm(Enum):
5 | TDES_2KEY = "TDES_2KEY"
6 | TDES_3KEY = "TDES_3KEY"
7 | AES_128 = "AES_128"
8 | AES_192 = "AES_192"
9 | AES_256 = "AES_256"
10 |
11 |
12 | class SymmetricKeyUsage(Enum):
13 | PEK = "PEK"
14 | BDK = "BDK"
15 | KEK = "KEK"
16 | KBPK = "KBPK"
17 |
18 |
19 | class AsymmetricKeyAlgorithm(Enum):
20 | RSA_2048 = "RSA_2046"
21 | RSA_3072 = "RSA_3072"
22 | RSA_4096 = "RSA_4096"
23 | ECC_NIST_P256 = "ECC_NIST_P256"
24 | ECC_NIST_P384 = "ECC_NIST_P384"
25 | ECC_NIST_P521 = "ECC_NIST_P521"
26 |
27 |
28 | class EccKeyAlgorithm(Enum):
29 | ECC_NIST_P256 = "ECC_NIST_P256"
30 | ECC_NIST_P384 = "ECC_NIST_P384"
31 | ECC_NIST_P521 = "ECC_NIST_P521"
32 |
33 |
34 | class RsaKeyAlgorithm(Enum):
35 | RSA_2048 = "RSA_2046"
36 | RSA_3072 = "RSA_3072"
37 | RSA_4096 = "RSA_4096"
38 |
39 |
40 | class AsymmetricKeyUsage(Enum):
41 | KEY_AGREEMENT_KEY = "KEY_AGREEMENT_KEY"
42 | DIGITAL_SIGNATURE = "DIGITAL_SIGNATURE"
43 | SIGN = "SIGN"
44 | VERIFY = "VERIFY"
45 |
46 |
47 | class KeyExchangeType(Enum):
48 | IMPORT_TR34_KEY_BLOCK = "IMPORT_TR34_KEY_BLOCK"
49 | EXPORT_TR34_KEY_BLOCK = "EXPORT_TR34_KEY_BLOCK"
50 | ECDH = "ECDH"
51 |
52 |
53 | class KeyDerivationFunction(Enum):
54 | NIST_SP800 = "NIST_SP800"
55 | ANSI_X963 = "ANSI_X963"
56 |
57 |
58 | class KeyDerivationHashAlgorithm(Enum):
59 | SHA_256 = "SHA_256"
60 | SHA_384 = "SHA_384"
61 | SHA_512 = "SHA_512"
62 |
--------------------------------------------------------------------------------
/key-import-export/rsa/export_app_with_signature/buildlayer.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | usage() {
5 | echo "AWS Lambda Layer Builder"
6 | echo "------------------------"
7 | echo "make-layer NAME RUNTIME PACKAGE_1 [PACKAGE_2] ..."
8 | echo "make-layer NAME RUNTIME MANIFEST"
9 | echo ""
10 | echo "Currently supported runtimes: nodejs*, python*"
11 | }
12 |
13 | # Check if Docker is installed
14 | if ! command -v docker >/dev/null 2>&1; then
15 | echo "Docker not found. Installing Docker..."
16 | sudo yum update -y
17 | sudo yum install -y docker
18 | sudo service docker start
19 | sudo usermod -a -G docker $USER
20 | newgrp docker
21 | fi
22 |
23 | if [[ "$#" -lt 3 ]]; then
24 | usage
25 | exit 1
26 | fi
27 |
28 | name="${1}"
29 | runtime="${2}"
30 | manifest="${3}"
31 |
32 | if test -f "$manifest"; then
33 | packages="${@:4}"
34 | else
35 | manifest=""
36 | packages="${@:3}"
37 | fi
38 |
39 | output_folder="$(pwd)/output"
40 | mkdir -p "$output_folder"
41 | echo "Output folder: $output_folder"
42 | docker_image="public.ecr.aws/sam/build-$runtime:latest"
43 | volume_params="-v $output_folder:/layer"
44 |
45 | if [[ $runtime == node* ]]; then
46 | package_folder="nodejs/"
47 | mkdir -p "$output_folder/$package_folder"
48 | if [[ -n "$manifest" ]]; then
49 | cp "$manifest" "$output_folder/$package_folder/package.json"
50 | fi
51 | install_command="pushd $package_folder; npm install; npm install --save $packages; popd"
52 | volume_params="$volume_params -v $HOME/.npmrc:/root/.npmrc"
53 |
54 | elif [[ $runtime == python* ]]; then
55 | package_folder="python/lib/$runtime/site-packages/"
56 | mkdir -p "$output_folder/$package_folder"
57 |
58 | if [[ -n "$manifest" ]]; then
59 | echo "Copying manifest file to $output_folder/requirements.txt"
60 | cp "$manifest" "$output_folder/requirements.txt"
61 | testpath="$output_folder/requirements.txt"
62 | chmod 0644 "$testpath"
63 | echo "Contents of $testpath:"
64 | cat "$testpath"
65 | else
66 | echo "Creating an empty requirements.txt file"
67 | touch "$output_folder/requirements.txt"
68 | chmod 0644 "$output_folder/requirements.txt"
69 | fi
70 |
71 | echo "Install command: $install_command"
72 | install_command="pip install -r /layer/requirements.txt -t /layer/$package_folder $packages"
73 | echo "Install command after update: $install_command"
74 | volume_params="$volume_params -v $HOME/.config/pip:/root/.config/pip -v $output_folder/requirements.txt:/layer/requirements.txt:ro"
75 |
76 | else
77 | usage
78 | exit 1
79 | fi
80 |
81 | echo "Building layer"
82 | zip_command="zip -r layer.zip python"
83 | docker run --rm $volume_params -w "/layer" "$docker_image" /bin/bash -c "$install_command && $zip_command"
84 |
85 | pushd "$output_folder"
86 | echo "Uploading layer $name to AWS"
87 | aws lambda publish-layer-version --layer-name "$name" --compatible-runtimes "$runtime" --zip-file "fileb://layer.zip"
88 | echo "Upload complete"
89 | popd
90 |
91 | echo "Cleaning up"
92 | rm -rf "$output_folder"
93 |
94 | echo "All done. Enjoy your shiny new Lambda layer!"
--------------------------------------------------------------------------------
/key-import-export/rsa/export_app_with_signature/images/arch-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/key-import-export/rsa/export_app_with_signature/images/arch-diagram.png
--------------------------------------------------------------------------------
/key-import-export/rsa/export_app_with_signature/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3>=1.26.0
2 | requests>=2.28.0
3 | cryptography>=3.4.8
4 | asn1crypto>=1.5.0
5 | oscrypto>=1.3.0
--------------------------------------------------------------------------------
/key-import-export/tr34/README.MD:
--------------------------------------------------------------------------------
1 | # Key Exchange Overiew from HSMs to AWS Payment Cryptography Service
2 |
3 | Following diagrams illustrate at high level the process of key exchange using TR34 for initial bootstrapping and then key exchange using TR31 of working keys.
4 |
5 | #### **TR-34 - Assymetric key exchange protocol to setup the KEK (Key Exchange Key) in AWS Payment Cryptography Service**
6 | 
7 |
8 | #### **TR-31 - Symmetric key exchange protocol to setp working keys (PEK, PVK, PGK etc)**
9 | 
10 |
--------------------------------------------------------------------------------
/key-import-export/tr34/import_app/Readme.md:
--------------------------------------------------------------------------------
1 | # Import Keys
2 |
3 | Importing keys is a prerequisite to run the JAVA samples.
4 |
5 | They Python samples are used to import clear text keys. The key import app already has sample clear text keys defined and can be run as is. The same keys and aliases defined in Python app here are used in the JAVA samples app. Look under `java_sdk_example/src/main/java/aws/sample/paymentcryptography/ServiceConstants.java` and `java_sdk_example/src/main/java/aws/sample/paymentcryptography/ServiceConstants.java`.
6 | You can change the clear text Hex keys in order to import your own key. If you do change the PEK or MAC keys in import app you will need to set the same values in `java_sdk_example/src/main/java/aws/sample/paymentcryptography/TerminalConstants.java` under "PEK" and "MAC_KEY_PLAIN_TEXT" variables.
7 |
8 | If you change BDK key, you will need to generate corresponding DUKPT variants along with KSN using the BDK key and set those variants in the `java_sdk_example/src/main/java/aws/sample/paymentcryptography/p2pe/key-ksn-data.json` with corresponding KSN. Refer to https://github.com/SoftwareVerde/java-dukpt for information on DUKPT keys and variants.
9 |
10 | ## Instructions
11 | Either one of the approaches below can be used to import the keys. You will need AWS credentials to run the import app.
12 |
13 | #### Using [Docker](https://docs.docker.com/get-docker/)
14 | If you have Docker installed, you can run the commands below. This can be used if you do not have Python installed or do not want to import the Python libraries needed for this app in your local system.
15 |
16 | ```
17 | cd samples-for-payment-cryptography-service/key-import-export
18 |
19 | docker build -t key-import-app .
20 |
21 | docker run -e AWS_ACCESS_KEY_ID= -e AWS_SECRET_ACCESS_KEY= -e AWS_DEFAULT_REGION=us-east-1 -it --rm key-import-app
22 | ```
23 | Once you run the commands above, it will import the keys and create aliases for those keys in AWS Payment Cryptography. You can now run the JAVA samples which use these keys.
24 |
25 | #### Using [Finch](https://github.com/runfinch/finch)
26 | If you have Finch installed, you can run the commands below. This can be used if you do not have Python installed or do not want to import the Python libraries needed for this app in your local system.
27 |
28 | ```
29 | cd samples-for-payment-cryptography-service/key-import-export
30 |
31 | finch build -t key-import-app .
32 |
33 | finch run -e AWS_ACCESS_KEY_ID= -e AWS_SECRET_ACCESS_KEY= -e AWS_DEFAULT_REGION=us-east-1 -it --rm key-import-app
34 | ```
35 |
36 | Once you run the commands above, it will import the keys and create aliases for those keys in AWS Payment Cryptography. You can now run the JAVA samples which use these keys.
37 |
38 |
39 | #### Using local Python to run the import app
40 | ```
41 | cd samples-for-payment-cryptography-service/key-import-export/tr34/import_app
42 |
43 | python -m venv .venv
44 | source .venv/bin/activate
45 | python3 -m pip install psec boto3 pycryptodome
46 | python3 apc_demo_keysetup.py
47 |
48 | ```
49 |
50 | With either approaches, you should get an output like below -
51 | ```
52 | *********Importing a KEK for importing subsequent keys*********
53 |
54 | ************************ DONE *****************
55 | Imported Key: 79adaef3212aadce312ace422accfefb
56 | Key Arn: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/3hn2ubebpeugbn22
57 | Reported KCV: 3C0A31
58 | Calculated KCV: 3C0A31
59 | Reported Type: TDES_2KEY
60 | KEK/KPBK/ZMK ARN: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/3hn2ubebpeugbn22
61 |
62 | *********Importing TDES BDK for DUKPT*********
63 |
64 | WRAPPED KEY IN TR-31 B0096B0TX00E0000A2C1AF40886230D79EAE236E54F22AA3617D8AF0ED7227872AAE1BA683917C48E530D9DA7F63B970
65 | Imported Key: 8a8349794c9ee9a4c2927098f249fed6
66 | Key Arn: arn:aws:payment-cryptography:us-west-2:XXXXXXXXXXXX:key/kiwl7juoitdeovx5
67 | Reported KCV: 9C8552
68 | Reported Type: TDES_2KEY
69 | TDES BDK ARN: arn:aws:payment-cryptography:us-west-2:XXXXXXXXXXXX:key/kiwl7juoitdeovx5
70 | Alias alias/MerchantTerminal_TDES_BDK
71 |
72 | *********Importing AES BDK for DUKPT*********
73 |
74 | WRAPPED KEY IN TR-31 B0112B0AX00E000033DFDAF93EBAE8FE476A2653A88DE4FF835C2114C97FEB2611A0D9A5F59988D3938C7E68597B3A07072C17A08ECF3B4D
75 | Imported Key: 8a8349794c9ee9a4c2927098f249fed6
76 | Key Arn: arn:aws:payment-cryptography:us-west-2:XXXXXXXXXXXX:key/u4lbzeh6onqscqjr
77 | Reported KCV: B32CFD
78 | Reported Type: AES_128
79 | AES BDK ARN: arn:aws:payment-cryptography:us-west-2:XXXXXXXXXXXX:key/u4lbzeh6onqscqjr
80 | Alias alias/MerchantTerminal_BDK_AES_128
81 |
82 | *********Importing a PEK for communicating with ATM*********
83 |
84 | WRAPPED KEY IN TR-31 B0096P0TB00E0000289A68560026472B58D327FAD108C28C0EF7672E2D7F21628BC201A89CC115F783738101301AC41B
85 | Imported Key: 545e2aadfd5ec42f2f5be5e3adc75e9b290252a1a219b380
86 | Key Arn: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/4lohwod3vn7u7fsq
87 | Reported KCV: C15FFF
88 | Reported Type: TDES_3KEY
89 | PEK(ATM PEK) ARN: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/4lohwod3vn7u7fsq
90 | Alias: alias/pinTranslateServicePek
91 |
92 | *********Importing a PEK for Pin Translate Service to Issuer communication. This service sits between between issuer and ATM) *********
93 |
94 | WRAPPED KEY IN TR-31 B0096P0TB00E0000A92C0E4FD9CCD3764829B749737406E4450251E6542D8BD916946AAB563A55E9936A8ED3D45E4FE9
95 | Imported Key: 545e2aadfd5ec42f2f5be5e3adc75e9b290252a1a219b380
96 | Key Arn: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/yq33o6suvqkr5wna
97 | Reported KCV: C15FFF
98 | Reported Type: TDES_3KEY
99 | PEK(ATM PEK) ARN: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/yq33o6suvqkr5wna
100 | Alias: alias/issuerPek
101 |
102 | *********Generating a PGK for generating a PVV*********
103 |
104 | Pin Verification Value ARN arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/bcpdzcpwgqkq6kbi
105 | Pin Verification Value Alias alias/issuerPinValidationKey
106 |
107 | *********Generating a MAC key for MAC verification********
108 |
109 | ************************ DONE *****************
110 | Imported Key: 75bdaef54587cae6563a5ce57b4b9f9f
111 | Key Arn: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/ryuqocdizyhkxgnt
112 | Reported KCV: 946B22
113 | Calculated KCV: 946B22
114 | Reported Type: TDES_2KEY
115 | MAC Key Alias: alias/tr31_macValidationKey
116 | MAC Key ARN: arn:aws:payment-cryptography:us-east-1:XXXXXXXXXXXX:key/ryuqocdizyhkxgnt
117 |
118 | *********Done*********
119 | ```
120 |
--------------------------------------------------------------------------------
/key-import-export/tr34/import_app/apc_demo_keysetup.py:
--------------------------------------------------------------------------------
1 | # python3 -m pip install psec
2 | # python3 -m pip install binascii
3 | # python3 -m pip install boto3
4 | # python3 -m pip install pycryptodome
5 |
6 |
7 | """
8 | This script is intended to import/generate all the keys needed for the AWS Payment Cryptography demo
9 | """
10 | import import_tr34_raw_key_to_apc as tr34
11 | import import_tr31_raw_key_to_apc as tr31
12 | import boto3
13 |
14 | """ Key encryption key which will be used to import subsequent keys """
15 | KEK = '79ADAEF3212AADCE312ACE422ACCFEFB'
16 | """ KEK = '8A8349794C9EE9A4C2927098F249FED6' """
17 |
18 | """ Base Derivation Key which will be used to generate DUKPT """
19 | BDK = '8A8349794C9EE9A4C2927098F249FED6'
20 |
21 | tdesBDKAlias = 'alias/MerchantTerminal_TDES_BDK'
22 | aesBDKAlias = 'alias/MerchantTerminal_BDK_AES_128'
23 |
24 | """ Pin Encryption Key. For the samples, the same key will be shared between ATM, Pin tranlation service and Issuer.
25 | This is to show that ATM can directly talk to issuer service to set and verify pin.
26 | ATM can also go through intermediate PinTranslateService which makes call to Issuer to set and verify Pin. """
27 | PEK = '545E2AADFD5EC42F2F5BE5E3ADC75E9B290252A1A219B380'
28 | pinTranslateServicePekAlias = "alias/pinTranslateServicePek"
29 | issuerPekAlias = 'alias/issuerPek'
30 |
31 | issuerGenerationAlias = 'alias/issuerPinValidationKey'
32 |
33 | """ ISO_9797_3_MAC_KEY for MAC verification """
34 | MAC = '75BDAEF54587CAE6563A5CE57B4B9F9F'
35 | """ MAC = '8A8349794C9EE9A4C2927098F249FED6' """
36 | macAlias = 'alias/macValidationKey'
37 |
38 | ARQC = "6786D3D6F2266E19B67302438ACE7551" # MDK
39 | arqcAlias = 'alias/arqcValidationKey'
40 |
41 | apc_client = boto3.client('payment-cryptography')
42 |
43 | def GeneratePvk(issuerGenerationAlias):
44 | #create PVK
45 | keyModesOfUse = {'Generate':True,'Verify':True}
46 | keyAttributes = {'KeyAlgorithm':'TDES_2KEY','KeyUsage':'TR31_V2_VISA_PIN_VERIFICATION_KEY','KeyClass':'SYMMETRIC_KEY','KeyModesOfUse':keyModesOfUse}
47 |
48 | PvkKeyARN = apc_client.create_key(Exportable=True,KeyAttributes=keyAttributes)['Key']['KeyArn']
49 |
50 | try:
51 | aliasList = apc_client.get_alias(AliasName=issuerGenerationAlias)
52 |
53 | if 'KeyArn' in aliasList['Alias']:
54 | keyDetails = apc_client.get_key(KeyIdentifier=aliasList['Alias']['KeyArn'])
55 | if (keyDetails['Key']['KeyState'] == 'CREATE_COMPLETE'):
56 | apc_client.delete_key(KeyIdentifier=aliasList['Alias']['KeyArn'], DeleteKeyInDays=3)
57 | apc_client.update_alias(AliasName=aliasList['Alias']['AliasName'],KeyArn=PvkKeyARN)
58 |
59 | except apc_client.exceptions.ResourceNotFoundException:
60 | aliasList = apc_client.create_alias(AliasName=issuerGenerationAlias,KeyArn=PvkKeyARN)
61 | return PvkKeyARN,issuerGenerationAlias
62 |
63 | if __name__ == "__main__":
64 |
65 | print("")
66 | print("*********Importing a KEK for importing subsequent keys*********")
67 | print("")
68 |
69 | kek_algorithm = "A" # A for AES, T for Triple DES
70 | tr31_versionID = 'D' if kek_algorithm == 'A' else 'B'
71 |
72 | tr34_response = tr34.importTr34("ONLINE",KEK,"E",kek_algorithm,"K0","B","","")
73 | print("KEK/KPBK/ZMK ARN:",tr34_response[0])
74 |
75 | print("")
76 | print("*********Importing TDES BDK for DUKPT*********")
77 | print("")
78 | response = tr31.importTR31(KEK,BDK,"E","B0","X","T","ONLINE",tr34_response[0],None,tdesBDKAlias,tr31_versionID)
79 | print("TDES BDK ARN:",response[0])
80 | print("Alias",response[1])
81 |
82 | print("")
83 | print("*********Importing AES BDK for DUKPT*********")
84 | print("")
85 | response = tr31.importTR31(KEK,BDK,"E","B0","X","A","ONLINE",tr34_response[0],None,aesBDKAlias,tr31_versionID)
86 | print("AES BDK ARN:",response[0])
87 | print("Alias",response[1])
88 |
89 |
90 | print("")
91 | print("*********Importing a PEK for communicating with ATM*********")
92 | print("")
93 | response = tr31.importTR31(KEK,PEK,"E","P0","B","T","ONLINE",tr34_response[0],None,pinTranslateServicePekAlias,tr31_versionID)
94 | print("PEK(ATM PEK) ARN:",response[0])
95 | print("Alias:",response[1])
96 |
97 | print("")
98 | print("*********Importing a PEK for Pin Translate Service to Issuer communication. This service sits between between issuer and ATM) *********")
99 | print("")
100 | response = tr31.importTR31(KEK,PEK,"E","P0","B","T","ONLINE",tr34_response[0],None,issuerPekAlias,tr31_versionID)
101 | print("PEK(ATM PEK) ARN:",response[0])
102 | print("Alias:",response[1])
103 |
104 | print("")
105 | print("*********Generating a PGK for generating a PVV*********")
106 | print("")
107 |
108 | response = GeneratePvk(issuerGenerationAlias)
109 |
110 | print("Pin Verification Value ARN",response[0])
111 | print("Pin Verification Value Alias",response[1])
112 |
113 | print("")
114 | print("*********Importing ARQC key for cryptogram validation*********")
115 | print("")
116 | response = tr31.importTR31(KEK,ARQC,"E","E0","X","T","ONLINE",tr34_response[0],None,arqcAlias,tr31_versionID)
117 | print("ARQC Validation Key ARN:",response[0])
118 | print("Alias:",response[1])
119 |
120 | print("")
121 | print("*********Generating a MAC key for MAC verification********")
122 | print("")
123 |
124 | response = tr31.importTR31(KEK,MAC,"E","M3","C","T","ONLINE",tr34_response[0],None,macAlias,tr31_versionID)
125 | print("MAC Key ARN:",response[0])
126 | print("Alias:",response[1])
127 | """ try:
128 | alias_res = apc_client.get_alias(AliasName=macAlias)
129 | except apc_client.exceptions.ResourceNotFoundException:
130 | alias_res = apc_client.create_alias(AliasName=macAlias)
131 |
132 |
133 | macResponse = apc_client.update_alias(AliasName=macAlias,KeyArn=response[0])
134 | print("MAC Key Alias:",macResponse['Alias']['AliasName'])
135 | print("MAC Key ARN:",macResponse['Alias']['KeyArn']) """
136 |
137 |
138 | print("")
139 | print("*********Done*********")
140 | print("")
141 |
142 |
--------------------------------------------------------------------------------
/key-import-export/tr34/import_app/import_tr31_raw_key_to_apc.py:
--------------------------------------------------------------------------------
1 | # python3 -m pip install psec
2 | # python3 -m pip install binascii
3 | # python3 -m pip install boto3
4 | # python3 -m pip install pycryptodome
5 |
6 | import psec
7 | import binascii
8 | import boto3
9 | import argparse
10 |
11 |
12 | def constructTr31Header(algo,exportMode,keyType,modeOfUse,versionID):
13 |
14 | #versionID = 'D' if algo == 'A' else 'B'
15 | length = '9999' #this library will overwrite it with the correct value
16 |
17 | header = versionID + length + keyType + algo + modeOfUse + "00" + exportMode + "0000"
18 |
19 | header = psec.tr31.Header(
20 | version_id=versionID, # Version B as recommended for TDES
21 | key_usage=keyType, # PIN Encryption
22 | algorithm=algo, # TDES
23 | mode_of_use=modeOfUse, # Encryption only
24 | version_num="00", # No version
25 | exportability=exportMode
26 | )
27 |
28 | return header
29 |
30 |
31 | def importTR31(kbpk_clearkey,wk_clearkey,exportmode,keytype,modeofuse,algorithm,runmode,kbpkkey_apcIdentifier,region,aliasName=None,tr31_versionID=None):
32 |
33 | kbpkkey = binascii.unhexlify(kbpk_clearkey.replace(" ",""))
34 |
35 | binaryWkKey = binascii.unhexlify(wk_clearkey.replace(" ",""))
36 |
37 | wrappedKey = (psec.tr31.wrap(kbpk=kbpkkey, header=constructTr31Header(algorithm,exportmode,keytype,modeofuse,tr31_versionID), key=binaryWkKey)).upper()
38 |
39 | print("WRAPPED KEY IN TR-31",wrappedKey)
40 |
41 | if runmode == 'OFFLINE':
42 | print('to complete run import key making sure to update key identifier and wrapped payload')
43 | else:
44 | if region==None or region == "":
45 | apc_client = boto3.client('payment-cryptography')
46 | else:
47 | apc_client = boto3.client('payment-cryptography',region_name=region)
48 |
49 | #clean up alias and associated keys
50 | if aliasName is not None:
51 | try:
52 | aliasList = apc_client.get_alias(AliasName=aliasName)
53 | except apc_client.exceptions.ResourceNotFoundException:
54 | aliasList = apc_client.create_alias(AliasName=aliasName)
55 |
56 | if 'KeyArn' in aliasList['Alias']:
57 | apc_client.update_alias(AliasName=aliasList['Alias']['AliasName'])
58 | keyDetails = apc_client.get_key(KeyIdentifier=aliasList['Alias']['KeyArn'])
59 | if (keyDetails['Key']['KeyState'] == 'CREATE_COMPLETE'):
60 | apc_client.delete_key(KeyIdentifier=aliasList['Alias']['KeyArn'], DeleteKeyInDays=3)
61 |
62 | imported_symmetric_key_res = apc_client.import_key(
63 | Enabled=True,
64 | KeyMaterial= {"Tr31KeyBlock": {"WrappingKeyIdentifier": kbpkkey_apcIdentifier,
65 | "WrappedKeyBlock": wrappedKey}}
66 | )
67 |
68 | print('Imported Key: ' + binaryWkKey.hex())
69 | print('Key Arn: ' + imported_symmetric_key_res['Key']['KeyArn'])
70 | print('Reported KCV: ' + imported_symmetric_key_res['Key']['KeyCheckValue'])
71 | print('Reported Type: ' + imported_symmetric_key_res['Key']['KeyAttributes']['KeyAlgorithm'])
72 |
73 | if aliasName is not None:
74 | apc_client.update_alias(AliasName=aliasName, KeyArn=imported_symmetric_key_res['Key']['KeyArn'])
75 |
76 | return imported_symmetric_key_res['Key']['KeyArn'],aliasName
77 |
78 |
79 | if __name__ == "__main__":
80 |
81 | print ("Sample code to encrypt a key in TR-31 format and import it into AWS Payment Cryptography")
82 | print ("This code assumes the key encryption key (KEK, KBPK, ZCMK) is 3DES but the key to import can be 3DES or AES.")
83 | print ("Can be run in the default mode where it generates the payload and directly makes all required service calls OR ")
84 | print ("can be run in offline mode where you can import the payload later on.")
85 |
86 |
87 | parser = argparse.ArgumentParser(prog='TR-31 Key Import Sample Code',
88 | description='Sample code to generate a TR-31 format and import it into AWS Payment Cryptography. This assumes that you have a clear text \
89 | version of the KBPK (Key Block Protection Key) also known as KEK/ZMK/ZMCK, have uploaded it to AWS Payment Cryptography and have its \
90 | associated keyIdentifier (keyARN or keyAlias). If you have not yet imported it, use import_raw_key_to_apc.py \
91 | mode which will directly import the key into the service but can only import 3DES keys. \
92 | Alternately, it can be run in offline mode where you specify the inputs and it will provide the payload to be run.',
93 | epilog='This is intended as sample code and comes with no warranty and is not intended for us with production keys.')
94 | parser.add_argument("--clearkey", help="Clear Text Key to import using TR-31", default="BA6DCE7F5E54F2A7CE45C41A64838C70")
95 | parser.add_argument("--kbpk_clearkey", help="Clear Text version of KBPK", default="8A8349794C9EE9A4C2927098F249FED6")
96 | parser.add_argument("--exportmode", "-e", help="Export Mode - E, S or N", default="E",choices=['E', 'S', 'N'])
97 | parser.add_argument("--algorithm", "-a", help="Algorithm of key - (T)DES or (A)ES", default="T", choices=['A', 'T','R'])
98 | parser.add_argument("--keytype", "-t", help="Key Type according to TR-31 norms. For instance K0, B0, etc", default="B0",choices=['K0', 'B0', 'D0','P0','D1'])
99 | parser.add_argument("--modeofuse", "-m", help="Mode of use according to TR-31 norms. For instance B (encrypt/decrypt),X (derive key)", default="X",choices=['B', 'X', 'N','E','D','G','C','V'])
100 | parser.add_argument("--runmode", help="Run mode. APC will directly import will offline will only produce tr-31 payload", default="APC",choices=['APC', 'OFFLINE'])
101 | parser.add_argument("--kbpkkey_apcIdentifier","-z", help="Key identifier for KEK that has already been imported into the service. It should have a keytype of K0.", default="",required=True)
102 |
103 | args = parser.parse_args()
104 |
105 | print ("Key to import:",args.clearkey)
106 | print ("Key Encryption Key (in cleartext)",args.kbpk_clearkey)
107 | print ("Key Encryption Key identifier on the service",args.kbpkkey_apcIdentifier)
108 | print ("Export Mode:",args.exportmode)
109 | print ("Key Type:",args.keytype)
110 | print ("Key Mode of use:",args.modeofuse)
111 | print ("Key Algorithm:",args.algorithm)
112 |
113 | region = args.kbpkkey_apcIdentifier.split(":")[3]
114 | print ("Region implied from keyARN",region)
115 |
116 | algorithm = args.algorithm
117 | tr31_versionID = 'D' if algorithm == 'A' else 'B'
118 | result = importTR31(kbpk_clearkey=args.kbpk_clearkey,wk_clearkey=args.clearkey,exportmode=args.exportmode, \
119 | algorithm=args.algorithm,keytype=args.keytype,modeofuse=args.modeofuse, runmode=args.runmode,kbpkkey_apcIdentifier=args.kbpkkey_apcIdentifier,region=region,tr31_versionID=tr31_versionID)
120 |
121 | #print('TR-31 Payload:',wrappedKey)
122 |
123 |
124 |
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/README.md:
--------------------------------------------------------------------------------
1 | # AWS Payment Cryptography terraform-samples
2 | This folder contains samples for [AWS Payment Cryptography](https://aws.amazon.com/payment-cryptography/) use-cases based on [HashiCorp Terraform](https://www.terraform.io/). Before exploring the use cases, ensure [service availability](https://aws.amazon.com/payment-cryptography/pricing/) in the specified AWS Region.
3 |
4 | Using AWS Payment Cryptography simplifies your implementation of cryptographic functions and key management used to secure data in payment processing in accordance with various PCI standards.
5 |
6 | Learn more about [AWS for PCI standards](https://aws.amazon.com/compliance/pci-dss-level-1-faqs/) and [AWS for FSI](https://aws.amazon.com/financial-services/)
7 |
8 | ## Use-cases
9 |
10 | AWS Payment Cryptography can be used to perform common data protection tasks for secure card payment processing. You can use AWS Payment Cryptography in several common card payment processing use-cases like:
11 |
12 | - Acquiring (Acquirer, Digital Wallet, Merchant, PSP or SoftPOS)
13 | - Issuing (ATM Driving, Issuer and processing)
14 | - Network and others (Network and Terminals for PSP/KIF Functionality)
15 |
16 | In this Sample, We will cover Acquirer use case for DBK/AWK as well as Issuers use cases for CVV and PIN
17 |
18 | ## Prerequisites
19 |
20 | - Get familiar with AWS Payment Cryptography [documentation](https://docs.aws.amazon.com/payment-cryptography/)
21 | - An AWS Account that will be used to create the keys
22 | - Access to your AWS Environment and specific resources
23 | - Terraform v.1.4.5 or later installed
24 |
25 | ## Architecture
26 |
27 | 
28 |
29 | ## Terraform Resources
30 |
31 | ## Providers
32 |
33 | | Name | Version |
34 | |------|---------|
35 | | [aws](#provider\_aws) | 5.54.0 |
36 |
37 | ## Modules
38 |
39 | | Name | Source | Version |
40 | |------|--------|---------|
41 | | [acquirer](#module\_acquirer) | ./acquirer | n/a |
42 | | [issuer-cvv](#module\_issuer-cvv) | ./issuer-cvv | n/a |
43 | | [issuer-pin](#module\_issuer-pin) | ./issuer-pin | n/a |
44 |
45 | ## Resources
46 |
47 | | Name | Type |
48 | |------|------|
49 | | [aws_cloudtrail.apc_trails](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail) | resource |
50 | | [aws_iam_instance_profile.ssm_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource |
51 | | [aws_iam_policy.payment_cryptography_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
52 | | [aws_iam_role.ssm_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
53 | | [aws_iam_role_policy_attachment.payment_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
54 | | [aws_iam_role_policy_attachment.ssm_managed_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
55 | | [aws_instance.thisinstance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource |
56 | | [aws_s3_bucket.apc_trails_s3_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
57 | | [aws_s3_bucket_policy.s3_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
58 | | [aws_security_group.ssm_endpoint_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
59 | | [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
60 | | [aws_security_group.thissg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
61 | | [aws_vpc_endpoint.ec2messages_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
62 | | [aws_vpc_endpoint.ssm_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
63 | | [aws_vpc_endpoint.ssmmessages_endpoint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
64 | | [aws_vpc_endpoint.thiscp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
65 | | [aws_vpc_endpoint.thisdp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint) | resource |
66 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
67 | | [aws_iam_policy.ssm_managed_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source |
68 | | [aws_iam_policy_document.ec2_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
69 | | [aws_iam_policy_document.payment_cryptography_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
70 | | [aws_iam_policy_document.s3_iam_policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
71 | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
72 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
73 |
74 | ## Inputs
75 |
76 | | Name | Description | Type | Default | Required |
77 | |------|-------------|------|---------|:--------:|
78 | | [ami\_id](#input\_ami\_id) | The ID of the Amazon Machine Image (AMI) to use for the EC2 instance | `string` | n/a | yes |
79 | | [application](#input\_application) | Name of the application | `string` | n/a | yes |
80 | | [instance\_type](#input\_instance\_type) | The type of EC2 instance to launch | `string` | `"t2.micro"` | no |
81 | | [key\_name](#input\_key\_name) | Name of the EC2 key pair | `string` | n/a | yes |
82 | | [s3\_name](#input\_s3\_name) | S3 Bucket name for AWS Payment Cryptography Log Archive | `string` | n/a | yes |
83 | | [subnet\_ids](#input\_subnet\_ids) | Eligible Subnets | `list(string)` | n/a | yes |
84 | | [trail\_name](#input\_trail\_name) | Trail name for AWS Payment Cryptography Log Archive | `string` | n/a | yes |
85 | | [trail\_prefix](#input\_trail\_prefix) | Trail prefix name for AWS Payment Cryptography Log Archive | `string` | n/a | yes |
86 | | [vpc\_cidr\_block](#input\_vpc\_cidr\_block) | Eligible CIDR ranges | `list(string)` | n/a | yes |
87 | | [vpc\_id](#input\_vpc\_id) | Customers can pass the vpc\_id here | `string` | n/a | yes |
88 |
89 | ## Outputs
90 |
91 | No outputs.
92 |
93 | ## Security
94 |
95 | See [CONTRIBUTING](../CONTRIBUTING.md) for more information.
96 |
97 | ## License
98 |
99 | This library is licensed under the MIT-0 License. See the [LICENSE](../LICENSE) file.
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/acquirer/keys_awk.tf:
--------------------------------------------------------------------------------
1 | resource "aws_paymentcryptography_key" "awk-key" {
2 | exportable = true
3 | key_attributes {
4 | key_algorithm = "TDES_3KEY"
5 | key_class = "SYMMETRIC_KEY"
6 | key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
7 | key_modes_of_use {
8 | encrypt = true
9 | decrypt = true
10 | wrap = true
11 | unwrap = true
12 |
13 | }
14 | }
15 | }
16 |
17 | resource "aws_paymentcryptography_key_alias" "awk-key" {
18 | alias_name = "alias/awk-key"
19 | key_arn = aws_paymentcryptography_key.bdk-key.arn
20 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/acquirer/keys_bdk.tf:
--------------------------------------------------------------------------------
1 | resource "aws_paymentcryptography_key" "bdk-key" {
2 | exportable = true
3 | key_attributes {
4 | key_algorithm = "TDES_2KEY"
5 | key_class = "SYMMETRIC_KEY"
6 | key_usage = "TR31_B0_BASE_DERIVATION_KEY"
7 | key_modes_of_use {
8 | derive_key = true
9 |
10 | }
11 | }
12 | }
13 |
14 | resource "aws_paymentcryptography_key_alias" "bdk-key" {
15 | alias_name = "alias/bdk-key"
16 | key_arn = aws_paymentcryptography_key.bdk-key.arn
17 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/application.tf:
--------------------------------------------------------------------------------
1 | # Create a security group
2 | # tfsec:ignore:AWS001
3 | resource "aws_security_group" "thissg" {
4 | name_prefix = "${var.application}-security-group"
5 | vpc_id = var.vpc_id
6 | description = "Security Group"
7 |
8 | ingress {
9 | from_port = 22
10 | to_port = 22
11 | protocol = "tcp"
12 | cidr_blocks = ["192.168.1.0/24"] # tfsec:ignore:AWS001 # Replace with your desired IP range
13 | description = "Allow SSH access from within the VPC"
14 | }
15 |
16 | ingress {
17 | from_port = 443
18 | to_port = 443
19 | protocol = "tcp"
20 | security_groups = [aws_security_group.ssm_endpoint_sg.id]
21 | description = "Allow HTTPS access from the SSM endpoint security group"
22 | }
23 |
24 | egress {
25 | from_port = 0
26 | to_port = 0
27 | protocol = "-1"
28 | cidr_blocks = ["192.168.1.0/24"] # tfsec:ignore:AWS001
29 | description = "Allow all outbound traffic"
30 | }
31 |
32 | tags = {
33 | Name = "${var.application}-security-group"
34 | }
35 | }
36 |
37 | # Create an EC2 instance in a private subnet
38 | resource "aws_instance" "thisinstance" {
39 | ami = var.ami_id
40 | instance_type = var.instance_type
41 |
42 | subnet_id = var.subnet_ids[0] # Selected one of the subnets for testing purposes
43 | vpc_security_group_ids = [aws_security_group.thissg.id]
44 | iam_instance_profile = aws_iam_instance_profile.ssm_profile.name
45 | key_name = var.key_name
46 | associate_public_ip_address = false # No public IP for a private subnet
47 |
48 | metadata_options {
49 | http_endpoint = "enabled"
50 | http_tokens = "required"
51 | http_put_response_hop_limit = 1
52 | }
53 |
54 | root_block_device {
55 | volume_type = "gp3"
56 | volume_size = 30
57 | encrypted = true
58 | }
59 |
60 | tags = {
61 | Name = "${var.application}-app-instance"
62 | }
63 | monitoring = true
64 | user_data = <<-EOF
65 | #!/bin/bash
66 | # Install the latest SSM Agent
67 | mkdir /tmp/ssm
68 | cd /tmp/ssm
69 | sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
70 | systemctl enable amazon-ssm-agent
71 | systemctl start amazon-ssm-agent
72 | EOF
73 | }
74 |
75 | resource "aws_iam_instance_profile" "ssm_profile" {
76 | name = "SSMInstanceProfile_app_${var.application}"
77 | role = aws_iam_role.ssm_role.name
78 | }
79 |
80 | resource "aws_iam_role" "ssm_role" {
81 | name = "SSMRole_app_${var.application}"
82 | assume_role_policy = data.aws_iam_policy_document.ec2_assume_role.json
83 | }
84 |
85 | resource "aws_iam_role_policy_attachment" "ssm_managed_instance" {
86 | role = aws_iam_role.ssm_role.name
87 | policy_arn = data.aws_iam_policy.ssm_managed_instance.arn
88 | }
89 |
90 | resource "aws_iam_role_policy_attachment" "payment_instance" {
91 | role = aws_iam_role.ssm_role.name
92 | policy_arn = aws_iam_policy.payment_cryptography_policy.arn
93 | }
94 |
95 | resource "aws_iam_policy" "payment_cryptography_policy" {
96 | name = "PaymentCryptographyPolicy"
97 | description = "Policy for Payment Cryptography service and EC2 network interface management"
98 | policy = data.aws_iam_policy_document.payment_cryptography_policy.json
99 | }
100 |
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/payment-cryptography-terraform-samples/architecture.png
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/data.tf:
--------------------------------------------------------------------------------
1 | data "aws_iam_policy" "ssm_managed_instance" {
2 | arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
3 | }
4 |
5 | data "aws_region" "current" {}
6 |
7 | data "aws_caller_identity" "current" {}
8 |
9 | data "aws_partition" "current" {}
10 |
11 | data "aws_iam_policy_document" "s3_iam_policy_document" {
12 | statement {
13 | sid = "AWSCloudTrailAclCheck"
14 | effect = "Allow"
15 |
16 | principals {
17 | type = "Service"
18 | identifiers = ["cloudtrail.amazonaws.com"]
19 | }
20 |
21 | actions = ["s3:GetBucketAcl"]
22 | resources = [aws_s3_bucket.apc_trails_s3_bucket.arn]
23 | condition {
24 | test = "StringEquals"
25 | variable = "aws:SourceArn"
26 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.trail_name}"]
27 | }
28 | }
29 |
30 | statement {
31 | sid = "AWSCloudTrailWrite"
32 | effect = "Allow"
33 |
34 | principals {
35 | type = "Service"
36 | identifiers = ["cloudtrail.amazonaws.com"]
37 | }
38 |
39 | actions = ["s3:PutObject"]
40 | resources = ["${aws_s3_bucket.apc_trails_s3_bucket.arn}/${var.trail_prefix}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"]
41 |
42 | condition {
43 | test = "StringEquals"
44 | variable = "s3:x-amz-acl"
45 | values = ["bucket-owner-full-control"]
46 | }
47 | condition {
48 | test = "StringEquals"
49 | variable = "aws:SourceArn"
50 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.trail_name}"]
51 | }
52 | }
53 | }
54 |
55 | data "aws_iam_policy_document" "ec2_assume_role" {
56 | statement {
57 | actions = ["sts:AssumeRole"]
58 | principals {
59 | type = "Service"
60 | identifiers = ["ec2.amazonaws.com"]
61 | }
62 | }
63 | }
64 |
65 | # tfsec:ignore:aws-iam-no-policy-wildcards
66 | data "aws_iam_policy_document" "payment_cryptography_policy" {
67 | statement {
68 | effect = "Allow"
69 | actions = ["payment-cryptography:*"]
70 | resources = ["*"]
71 | }
72 |
73 | statement {
74 | effect = "Allow"
75 | actions = [
76 | "ec2:DescribeNetworkInterfaces",
77 | "ec2:CreateNetworkInterface",
78 | "ec2:DeleteNetworkInterface",
79 | "ec2:DescribeInstances",
80 | "ec2:AttachNetworkInterface"
81 | ]
82 | resources = ["*"]
83 | }
84 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/issuer-cvv/keys_cvv.tf:
--------------------------------------------------------------------------------
1 | resource "aws_paymentcryptography_key" "cvk-key" {
2 | exportable = true
3 | key_attributes {
4 | key_algorithm = "TDES_2KEY"
5 | key_class = "SYMMETRIC_KEY"
6 | key_usage = "TR31_C0_CARD_VERIFICATION_KEY"
7 | key_modes_of_use {
8 | generate = true
9 | verify = true
10 |
11 | }
12 | }
13 | }
14 |
15 | resource "aws_paymentcryptography_key_alias" "cvk-key" {
16 | alias_name = "alias/cvk-key"
17 | key_arn = aws_paymentcryptography_key.cvk-key.arn
18 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/issuer-pin/keys_pin.tf:
--------------------------------------------------------------------------------
1 | resource "aws_paymentcryptography_key" "pvk-key" {
2 | exportable = true
3 | key_attributes {
4 | key_algorithm = "TDES_2KEY"
5 | key_class = "SYMMETRIC_KEY"
6 | key_usage = "TR31_V2_VISA_PIN_VERIFICATION_KEY"
7 | key_modes_of_use {
8 | generate = true
9 | verify = true
10 |
11 | }
12 | }
13 | }
14 |
15 | resource "aws_paymentcryptography_key_alias" "pvk-key" {
16 | alias_name = "alias/pvk-key"
17 | key_arn = aws_paymentcryptography_key.pvk-key.arn
18 | }
19 |
20 | resource "aws_paymentcryptography_key" "pek-key" {
21 | exportable = true
22 | key_attributes {
23 | key_algorithm = "TDES_3KEY"
24 | key_class = "SYMMETRIC_KEY"
25 | key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
26 | key_modes_of_use {
27 | encrypt = true
28 | decrypt = true
29 | wrap = true
30 | unwrap = true
31 |
32 | }
33 | }
34 | }
35 |
36 | resource "aws_paymentcryptography_key_alias" "pek-key" {
37 | alias_name = "alias/pek-key"
38 | key_arn = aws_paymentcryptography_key.pek-key.arn
39 | }
40 |
41 | resource "aws_paymentcryptography_key" "pek-iwk-key" {
42 | exportable = true
43 | key_attributes {
44 | key_algorithm = "TDES_3KEY"
45 | key_class = "SYMMETRIC_KEY"
46 | key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
47 | key_modes_of_use {
48 | encrypt = true
49 | decrypt = true
50 | wrap = true
51 | unwrap = true
52 |
53 | }
54 | }
55 | }
56 |
57 | resource "aws_paymentcryptography_key_alias" "pek-iwk-key" {
58 | alias_name = "alias/pek-iwk-key"
59 | key_arn = aws_paymentcryptography_key.pek-iwk-key.arn
60 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/log_archive.tf:
--------------------------------------------------------------------------------
1 | # tfsec:ignore:aws-s3-enable-bucket-logging
2 | resource "aws_s3_bucket" "apc_trails_s3_bucket" {
3 | bucket = var.s3_name
4 | force_destroy = true
5 |
6 | }
7 |
8 | resource "aws_s3_bucket_versioning" "this" {
9 | bucket = aws_s3_bucket.apc_trails_s3_bucket.id
10 | versioning_configuration {
11 | status = "Enabled"
12 | }
13 | }
14 |
15 | resource "aws_s3_bucket_public_access_block" "this" {
16 | bucket = aws_s3_bucket.apc_trails_s3_bucket.id
17 |
18 | ignore_public_acls = true
19 | block_public_acls = true
20 | block_public_policy = true
21 | restrict_public_buckets = true
22 | }
23 |
24 | # tfsec:ignore:aws-s3-encryption-customer-key
25 | resource "aws_s3_bucket_server_side_encryption_configuration" "apc_trails_s3_bucket" {
26 | bucket = aws_s3_bucket.apc_trails_s3_bucket.id
27 |
28 | rule {
29 | apply_server_side_encryption_by_default {
30 | sse_algorithm = "AES256"
31 | }
32 | }
33 | }
34 |
35 | resource "aws_s3_bucket_policy" "s3_bucket_policy" {
36 | bucket = aws_s3_bucket.apc_trails_s3_bucket.id
37 | policy = data.aws_iam_policy_document.s3_iam_policy_document.json
38 | }
39 |
40 | # tfsec:ignore:aws-cloudtrail-enable-at-rest-encryption
41 | # tfsec:ignore:aws-cloudtrail-ensure-cloudwatch-integration
42 | resource "aws_cloudtrail" "apc_trails" {
43 | depends_on = [aws_s3_bucket_policy.s3_bucket_policy]
44 |
45 | name = var.trail_name
46 | s3_bucket_name = aws_s3_bucket.apc_trails_s3_bucket.id
47 | s3_key_prefix = var.trail_prefix
48 | include_global_service_events = false
49 | enable_log_file_validation = true
50 | is_multi_region_trail = true
51 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/main.tf:
--------------------------------------------------------------------------------
1 | ### Deploy keys
2 |
3 | module "acquirer" {
4 | source = "./acquirer"
5 | }
6 |
7 | module "issuer-cvv" {
8 | source = "./issuer-cvv"
9 | }
10 |
11 | module "issuer-pin" {
12 | source = "./issuer-pin"
13 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/outputs.tf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/payment-cryptography-terraform-samples/outputs.tf
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/providers.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = "us-east-1"
3 | }
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/pvlink.tf:
--------------------------------------------------------------------------------
1 | ## SG For Endpoints
2 | resource "aws_security_group" "this" {
3 | name_prefix = "payment-crypto-endpoint-"
4 | vpc_id = var.vpc_id
5 | description = "Payment Cryptography Endpoint Security Group"
6 |
7 | ingress {
8 | from_port = 443
9 | to_port = 443
10 | protocol = "tcp"
11 | cidr_blocks = var.vpc_cidr_block
12 | description = "Allow HTTPS traffic from VPC CIDR Block"
13 |
14 | }
15 |
16 | ingress {
17 | from_port = 443
18 | to_port = 443
19 | protocol = "tcp"
20 | security_groups = ["${aws_security_group.thissg.id}"] # SG For instance
21 | description = "Allow HTTPS traffic from EC2 instance Security Group"
22 | }
23 |
24 | egress {
25 | from_port = 0
26 | to_port = 0
27 | protocol = "-1"
28 | cidr_blocks = ["192.168.1.0/24"]
29 | description = "Allow all outbound traffic"
30 |
31 | }
32 |
33 | tags = {
34 | Name = "Payment Cryptography Endpoint Security Group"
35 | }
36 | }
37 |
38 | # Payment Cryptography Control Plane
39 | resource "aws_vpc_endpoint" "thiscp" {
40 | vpc_id = var.vpc_id
41 | service_name = "com.amazonaws.${data.aws_region.current.name}.payment-cryptography.controlplane"
42 | vpc_endpoint_type = "Interface"
43 |
44 | security_group_ids = [
45 | aws_security_group.this.id,
46 | ]
47 |
48 | subnet_ids = var.subnet_ids
49 |
50 | private_dns_enabled = true
51 | }
52 |
53 | # Payment Cryptography Data Plane
54 | resource "aws_vpc_endpoint" "thisdp" {
55 | vpc_id = var.vpc_id
56 | service_name = "com.amazonaws.${data.aws_region.current.name}.payment-cryptography.dataplane"
57 | vpc_endpoint_type = "Interface"
58 |
59 | security_group_ids = [
60 | aws_security_group.this.id,
61 | ]
62 |
63 | subnet_ids = var.subnet_ids
64 |
65 | private_dns_enabled = true
66 | }
67 |
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/ssm.tf:
--------------------------------------------------------------------------------
1 | # Create a VPC endpoint for Systems Manager
2 | resource "aws_vpc_endpoint" "ssm_endpoint" {
3 | vpc_id = var.vpc_id
4 | service_name = "com.amazonaws.${data.aws_region.current.name}.ssm"
5 | vpc_endpoint_type = "Interface"
6 |
7 | security_group_ids = [
8 | aws_security_group.ssm_endpoint_sg.id,
9 | ]
10 |
11 | private_dns_enabled = true
12 | subnet_ids = var.subnet_ids
13 |
14 | tags = {
15 | Name = "${var.application}-ssm-endpoint"
16 | }
17 | }
18 |
19 | resource "aws_vpc_endpoint" "ec2messages_endpoint" {
20 | vpc_id = var.vpc_id
21 | service_name = "com.amazonaws.${data.aws_region.current.name}.ec2messages"
22 | vpc_endpoint_type = "Interface"
23 |
24 | security_group_ids = [
25 | aws_security_group.ssm_endpoint_sg.id,
26 | ]
27 |
28 | private_dns_enabled = true
29 | subnet_ids = var.subnet_ids
30 |
31 | tags = {
32 | Name = "${var.application}-ssm-endpoint"
33 | }
34 | }
35 |
36 | resource "aws_vpc_endpoint" "ssmmessages_endpoint" {
37 | vpc_id = var.vpc_id
38 | service_name = "com.amazonaws.${data.aws_region.current.name}.ssmmessages"
39 | vpc_endpoint_type = "Interface"
40 |
41 | security_group_ids = [
42 | aws_security_group.ssm_endpoint_sg.id,
43 | ]
44 |
45 | private_dns_enabled = true
46 | subnet_ids = var.subnet_ids
47 |
48 | tags = {
49 | Name = "${var.application}-ssm-endpoint"
50 | }
51 | }
52 |
53 | # Security group for the VPC endpoint
54 | resource "aws_security_group" "ssm_endpoint_sg" {
55 | name_prefix = "${var.application}-ssm-endpoint-sg-"
56 | vpc_id = var.vpc_id
57 | description = "Security group for SSM endpoint"
58 | ingress {
59 | from_port = 443
60 | to_port = 443
61 | protocol = "tcp"
62 | cidr_blocks = var.vpc_cidr_block
63 | description = "Allow inbound from VPC"
64 | }
65 | tags = {
66 | Name = "${var.application}-ssm-endpoint-sg"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/tfsec_output.txt:
--------------------------------------------------------------------------------
1 | [0m [1mtimings[0m
2 | ──────────────────────────────────────────
3 | [0m[0m [2mdisk i/o [0m 141.814µs
4 | [0m[0m [2mparsing [0m 6.776173ms
5 | [0m[0m [2madaptation [0m 656.735µs
6 | [0m[0m [2mchecks [0m 7.231684ms
7 | [0m[0m [2mtotal [0m 14.806406ms
8 | [0m
9 | [0m [1mcounts[0m
10 | ──────────────────────────────────────────
11 | [0m[0m [2mmodules downloaded [0m 0
12 | [0m[0m [2mmodules processed [0m 4
13 | [0m[0m [2mblocks processed [0m 53
14 | [0m[0m [2mfiles read [0m 13
15 | [0m
16 | [0m [1mresults[0m
17 | ──────────────────────────────────────────
18 | [0m[0m [2mpassed [0m 30
19 | [0m[0m [2mignored [0m 12
20 | [0m[0m [2mcritical [0m 0
21 | [0m[0m [2mhigh [0m 0
22 | [0m[0m [2mmedium [0m 0
23 | [0m[0m [2mlow [0m 0
24 | [0m
25 | [0m
26 | [32m[1mNo problems detected!
27 |
28 | [0m
--------------------------------------------------------------------------------
/payment-cryptography-terraform-samples/variables.tf:
--------------------------------------------------------------------------------
1 | ## Networking variables
2 |
3 | variable "vpc_id" {
4 | type = string
5 | description = "Customers can pass the vpc_id here"
6 | }
7 |
8 | variable "subnet_ids" {
9 | type = list(string)
10 | description = "Eligible Subnets"
11 | }
12 |
13 | variable "vpc_cidr_block" {
14 | type = list(string)
15 | description = "Eligible CIDR ranges"
16 | }
17 |
18 | ## Log Archive variables
19 |
20 | variable "s3_name" {
21 | type = string
22 | description = "S3 Bucket name for APC Log Archive"
23 | }
24 |
25 | variable "trail_name" {
26 | type = string
27 | description = "Trail name for APC Log Archive"
28 | }
29 |
30 | variable "trail_prefix" {
31 | type = string
32 | description = "Trail prefix name for APC Log Archive"
33 | }
34 |
35 | ## EC2 Instance variables
36 |
37 | variable "key_name" {
38 | description = "Name of the EC2 key pair"
39 | type = string
40 | }
41 |
42 | variable "application" {
43 | description = "Name of the application"
44 | type = string
45 | }
46 |
47 | variable "ami_id" {
48 | description = "The ID of the Amazon Machine Image (AMI) to use for the EC2 instance"
49 | type = string
50 | }
51 |
52 | variable "instance_type" {
53 | description = "The type of EC2 instance to launch"
54 | type = string
55 | default = "t2.micro"
56 | }
57 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/README.md:
--------------------------------------------------------------------------------
1 | # AWS Payment Crypto ECDH Pin Set/Reveal flows
2 |
3 | This repository contains a sample for three specific AWS Payment Cryptography Use Cases:
4 | 1. RESET PIN: When a user forgets it's PIN and you want to randomly generate a new one and show it to them, storing the PVV on the backend.
5 | 2. SET PIN: When a user wants to set an arbitrary PIN, and the backend stores the PVV.
6 | 3. REVEAL PIN: When you want to obtain the pinblock from an encrypted pinblock for some very niche and specific use-cases
7 |
8 | These use cases are implemented using ECDH Key Agreement to derive a symmetric key which is used to encrypt the pinblock between the device and AWS Payment Cryptography using ISO_FORMAT_4. As part of the implementation a Certificate Authority is needed, this demo implements it using AWS Private CA with short-lived certificates.
9 |
10 | ## Flows
11 | ### Reset PIN
12 | 
13 |
14 | ### Select PIN
15 | 
16 |
17 | ### Reveal PIN
18 | 
19 |
20 | ## Cost
21 | The following costs represent the us-east-1 (North Virginia) AWS Region, prices may vary across regions.
22 |
23 | 1. AWS Private CA for short-lived certificates has a pricing of US$ 50 (hourly prorated) per month.
24 | 2. AWS Private CA charges 0.058 for each short-lived certificate (This demo issues 4 certificates: 1 for CA setup, 1 for each flow)
25 | 3. Each AWS Payment Cryptography key is charged US$ 1 per key (hourly prorated) per month, and this demo uses 4 keys
26 | 4. Each AWS Payment Cryptography API is charged at US$ 2 per 10,000 API Calls, this demo does less than 50
27 |
28 | You can stop the costs by calling the tear_down.py script included, which deletes all created assets.
29 |
30 | If you execute this demo and immediately call tear_down.py, it will have an overall cost of 0.24 USD approximated.
31 |
32 | ## Setup
33 |
34 | Generate a Python Virtual Environment and install required libraries
35 | ```
36 | python3 -m venv .venv
37 | source .venv/bin/activate
38 | pip3 install -r requirements.txt
39 | ```
40 | You also need local AWS Credentials that have access to AWS Payment Cryptography and AWS Private CA
41 |
42 | ## Permissions - IAM Policy
43 | To execute this demo you need the following permissions
44 | ```
45 | {
46 | "Version": "2012-10-17",
47 | "Statement": [
48 | {
49 | "Effect": "Allow",
50 | "Action": [
51 | "acm-pca:ListCertificateAuthorities",
52 | "acm-pca:CreateCertificateAuthority",
53 | "acm-pca:DescribeCertificateAuthority",
54 | "acm-pca:TagCertificateAuthority",
55 | "acm-pca:GetCertificateAuthorityCsr",
56 | "acm-pca:IssueCertificate",
57 | "acm-pca:GetCertificate",
58 | "acm-pca:ImportCertificateAuthorityCertificate",
59 | "acm-pca:GetCertificateAuthorityCertificate",
60 | "payment-cryptography:GetAlias",
61 | "payment-cryptography:ImportKey",
62 | "payment-cryptography:CreateAlias",
63 | "payment-cryptography:CreateKey",
64 | "kms:GenerateRandom",
65 | "payment-cryptography:GetPublicKeyCertificate",
66 | "payment-cryptography:GeneratePinData",
67 | "payment-cryptography:TranslatePinData",
68 | "acm-pca:ListTags",
69 | "acm-pca:UpdateCertificateAuthority",
70 | "acm-pca:DeleteCertificateAuthority",
71 | "payment-cryptography:ListKeys",
72 | "payment-cryptography:ListTagsForResource",
73 | "payment-cryptography:DeleteKey",
74 | "payment-cryptography:ListAliases",
75 | "payment-cryptography:TagResource",
76 | "payment-cryptography:DeleteAlias"
77 | ],
78 | "Resource": "*"
79 | }
80 | ]
81 | }
82 |
83 | ```
84 |
85 | ## Execute
86 | Simulate the three flows of this Demo. This will create a Private CA and the needed AWS Payment Cryptography cryptographic key the first time is ran.
87 | These keys and CA will stay created until you call the tear_down.py script.
88 |
89 | ```
90 | python3 payment_crypto/main.py
91 | ```
92 |
93 | ## Clean Up
94 | Clean up resources (including CA)
95 | ```
96 | python3 payment_crypto/tear_down.py
97 | ```
98 |
99 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/images/PIN-Reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/python_sdk_example/ecdh_flows/images/PIN-Reset.png
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/images/PIN-Reveal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/python_sdk_example/ecdh_flows/images/PIN-Reveal.png
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/images/PIN-Select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/python_sdk_example/ecdh_flows/images/PIN-Select.png
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/python_sdk_example/ecdh_flows/payment_crypto/__init__.py
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/ecdh/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/samples-for-payment-cryptography-service/fa01650910fe0839656fbae7b115c8971e4dceab/python_sdk_example/ecdh_flows/payment_crypto/ecdh/__init__.py
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/ecdh/client.py:
--------------------------------------------------------------------------------
1 | import psec
2 | import psec.pinblock
3 |
4 | from payment_crypto.ecdh.backend import Backend
5 | from payment_crypto.ecdh.crypto_utils import CryptoUtils
6 |
7 |
8 | class Client:
9 |
10 | @staticmethod
11 | def set_pin(pin: str, pan: str, backend: Backend):
12 | # Generate local ecdh key-pair
13 | private_key, public_key = CryptoUtils.generate_ecdh_key_pair()
14 | # Generate shared info
15 | shared_info = CryptoUtils.generate_shared_info()
16 | # get AWS Payment Cryptography Certificates
17 | apc_ca_certificate, apc_certificate = backend.get_apc_certificates()
18 | # Derive ECDH symmetric key
19 | derived_key = CryptoUtils.generate_ecc_symmetric_key_client(apc_certificate, private_key, shared_info)
20 | # encrypt pinblock with Derived Key
21 | encrypted_pin_block = CryptoUtils.generate_pin_block_iso_4(derived_key, pin, pan)
22 | # generate Certificate Signing Request
23 | csr = CryptoUtils.generate_certificate_signing_request(private_key)
24 | # tell the backend to set the PIN
25 | backend.set_pin(pan, encrypted_pin_block, csr, shared_info)
26 |
27 | @staticmethod
28 | def pin_reveal(pek_pinblock, pan, backend):
29 | # Generate local ecdh key-pair
30 | private_key, public_key = CryptoUtils.generate_ecdh_key_pair()
31 | # Generate shared info
32 | shared_info = CryptoUtils.generate_shared_info()
33 | # get AWS Payment Cryptography Certificates
34 | apc_ca_certificate, apc_certificate = backend.get_apc_certificates()
35 | # Derive ECDH symmetric key
36 | derived_key = CryptoUtils.generate_ecc_symmetric_key_client(apc_certificate, private_key, shared_info)
37 | # generate Certificate Signing Request
38 | csr = CryptoUtils.generate_certificate_signing_request(private_key)
39 | # get ECDH encrypted pinblock
40 | encrypted_pinblock = backend.get_ecdh_pinblock(pan, pek_pinblock, csr, shared_info)
41 | bytes_pinblock = bytes.fromhex(encrypted_pinblock)
42 | # decrypt pinblock with derived_key and print
43 | pin = psec.pinblock.decipher_pinblock_iso_4(derived_key, bytes_pinblock, pan)
44 | return pin
45 |
46 | @staticmethod
47 | def pin_reset(pan, backend):
48 | # Generate local ecdh key-pair
49 | private_key, public_key = CryptoUtils.generate_ecdh_key_pair()
50 | # Generate shared info
51 | shared_info = CryptoUtils.generate_shared_info()
52 | # get AWS Payment Cryptography Certificates
53 | apc_ca_certificate, apc_certificate = backend.get_apc_certificates()
54 | # Derive ECDH symmetric key
55 | derived_key = CryptoUtils.generate_ecc_symmetric_key_client(apc_certificate, private_key, shared_info)
56 | # generate Certificate Signing Request
57 | csr = CryptoUtils.generate_certificate_signing_request(private_key)
58 | # get ECDH encrypted pinblock
59 | encrypted_pinblock = backend.reset_pin(pan, csr, shared_info)
60 | bytes_pinblock = bytes.fromhex(encrypted_pinblock)
61 | # decrypt pinblock with derived_key and print
62 | pin = psec.pinblock.decipher_pinblock_iso_4(derived_key, bytes_pinblock, pan)
63 | return pin
64 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/ecdh/crypto_utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | from cryptography.hazmat.primitives.asymmetric import ec
3 | from cryptography import x509
4 | from cryptography.x509.oid import NameOID
5 | from cryptography.hazmat.primitives import hashes, serialization
6 | from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
7 | # from Crypto.Hash import CMAC
8 | # from Crypto.Cipher import AES
9 | import psec
10 | import psec.pinblock
11 | import binascii
12 | import base64
13 | import time
14 | import secrets
15 |
16 |
17 | class CryptoUtils:
18 |
19 | @staticmethod
20 | def generate_certificate_signing_request(private_key):
21 | # Generate a CSR
22 | csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
23 | # Provide various details about who we are.
24 | x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
25 | x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
26 | x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
27 | x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"),
28 | x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"),
29 | ])).sign(private_key, hashes.SHA256())
30 |
31 | return csr
32 |
33 | @staticmethod
34 | def generate_ecdh_key_pair():
35 | private_key = ec.generate_private_key(curve=ec.SECP256R1())
36 | public_key = private_key.public_key()
37 | return private_key, public_key
38 |
39 | @staticmethod
40 | def generate_shared_info():
41 | return secrets.token_bytes(32)
42 | # you could generate this random with KMS as well
43 | # kms_client = boto3.client('kms')
44 | # print(kms_client.generate_random(NumberOfBytes=32)["Plaintext"])
45 | # return kms_client.generate_random(NumberOfBytes=32)["Plaintext"]
46 |
47 | @staticmethod
48 | def generate_ecc_symmetric_key_client(certificate, krd_private_key, info):
49 | pem = base64.b64decode(certificate)
50 | certificate = x509.load_pem_x509_certificate(pem)
51 | shared_key = krd_private_key.exchange(
52 | ec.ECDH(), certificate.public_key())
53 | # Perform key derivation.
54 | derived_key = ConcatKDFHash( # ConcatKDFHash also known as NIST SP 800-56Ar3
55 | algorithm=hashes.SHA512(),
56 | length=16, # 16 is AES-128, 32 is AES-256
57 | otherinfo=info,
58 | ).derive(shared_key)
59 |
60 | # KCV code
61 | # print(binascii.hexlify(derived_key))
62 | # cobj = CMAC.new(derived_key, ciphermod=AES)
63 | # cobj.update(binascii.unhexlify('00000000000000000000000000000000'))
64 | # kcv = cobj.hexdigest()[0:6].upper()
65 | # print('Derived Key on Desktop - Calculated KCV(CMAC): ' + cobj.hexdigest()[0:6])
66 |
67 | return derived_key
68 |
69 | @staticmethod
70 | def generate_pin_block_iso_4(derived_key, pin, pan):
71 | return binascii.hexlify(psec.pinblock.encipher_pinblock_iso_4(derived_key, pin, pan)).decode().upper()
72 |
73 | @staticmethod
74 | def get_apc_ecdh_parameters(ca_key_arn, signed_client_certificate, shared_info):
75 | return {
76 | "WrappedKeyMaterial": {"DiffieHellmanSymmetricKey": {"CertificateAuthorityPublicKeyIdentifier": ca_key_arn,
77 | "KeyAlgorithm": "AES_128",
78 | "KeyDerivationFunction": "NIST_SP800",
79 | "KeyDerivationHashAlgorithm": "SHA_512",
80 | "PublicKeyCertificate": base64.b64encode(
81 | signed_client_certificate.encode('ascii')).decode(
82 | 'ascii'),
83 | "SharedInformation": binascii.hexlify(
84 | shared_info).decode().upper()}}}
85 |
86 | @staticmethod
87 | def sign_with_private_ca(ca_arn, csr, validity, template="arn:aws:acm-pca:::template/EndEntityCertificate/V1"):
88 | """
89 | Signs the client-side ECDH Key with AWS Private CA and returns the Certificate and Certificate Chain
90 | :param validity:
91 | :param ca_arn:
92 | :param csr: Certificate Signing Request
93 | :param template: Template ARN to use for the certificate
94 | :return:
95 | """
96 | client = boto3.client('acm-pca')
97 | response = client.issue_certificate(
98 | CertificateAuthorityArn=ca_arn,
99 | Csr=csr,
100 | TemplateArn=template,
101 | SigningAlgorithm='SHA256WITHECDSA',
102 | Validity=validity
103 | )
104 | certificate_arn = response['CertificateArn']
105 | time.sleep(0.5)
106 |
107 | while 1:
108 | try:
109 | certificate_response = client.get_certificate(CertificateArn=certificate_arn,
110 | CertificateAuthorityArn=ca_arn)
111 | if 'CertificateChain' in certificate_response:
112 | chain = certificate_response['CertificateChain']
113 | else:
114 | chain = None
115 | return certificate_response['Certificate'], chain
116 | except client.exceptions.RequestInProgressException:
117 | time.sleep(0.1)
118 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/ecdh/setup.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import boto3
3 | from payment_crypto.ecdh.crypto_utils import CryptoUtils
4 | import time
5 |
6 | KEY_ALIAS_PREFIX = "pindemo-"
7 | TAG_KEY = "pindemo"
8 | controlplane_client = boto3.client("payment-cryptography")
9 | private_ca = boto3.client("acm-pca")
10 |
11 |
12 | def import_ca_key_to_apc(certificate_authority):
13 | key_arn = get_key_by_alias("ca_key")
14 | if key_arn is None:
15 | print("Importing CA Key")
16 | key_arn = controlplane_client.import_key(Enabled=True, KeyMaterial={
17 | 'RootCertificatePublicKey': {
18 | 'KeyAttributes': {
19 | 'KeyAlgorithm': 'ECC_NIST_P256',
20 | 'KeyClass': 'PUBLIC_KEY',
21 | 'KeyModesOfUse': {
22 | 'Verify': True,
23 | },
24 | 'KeyUsage': 'TR31_S0_ASYMMETRIC_KEY_FOR_DIGITAL_SIGNATURE',
25 | },
26 | 'PublicKeyCertificate': base64.b64encode(certificate_authority.encode('UTF-8')).decode('UTF-8')
27 | }
28 | }, KeyCheckValueAlgorithm='ANSI_X9_24', Tags=[{"Key":TAG_KEY, "Value":"1"}])['Key']['KeyArn']
29 | create_alias("ca_key", key_arn)
30 |
31 | return key_arn
32 |
33 |
34 | def get_key_by_alias(alias):
35 | alias = "alias/%s%s" % (KEY_ALIAS_PREFIX, alias)
36 | try:
37 | answer = controlplane_client.get_alias(AliasName=alias)
38 | return answer["Alias"]["KeyArn"]
39 | except:
40 | return None
41 |
42 |
43 | def create_alias(alias, key_arn):
44 | alias = "alias/%s%s" % (KEY_ALIAS_PREFIX, alias)
45 | controlplane_client.create_alias(AliasName=alias, KeyArn=key_arn)
46 |
47 |
48 | def apc_generate_pgk():
49 | alias = "pgk"
50 | key_arn = get_key_by_alias(alias)
51 | if key_arn is None:
52 | print("Creating new PGK")
53 | key_arn = controlplane_client.create_key(Exportable=True,
54 | KeyAttributes={
55 | "KeyAlgorithm": "TDES_2KEY",
56 | "KeyUsage": "TR31_V2_VISA_PIN_VERIFICATION_KEY",
57 | "KeyClass": "SYMMETRIC_KEY",
58 | "KeyModesOfUse": {"Generate": True, "Verify": True}
59 | },
60 | Tags=[{"Key": TAG_KEY, "Value": "1"}])['Key']['KeyArn']
61 | create_alias(alias, key_arn)
62 | return key_arn
63 |
64 |
65 | def apc_generate_pek():
66 | alias = "pek"
67 | key_arn = get_key_by_alias(alias)
68 | if key_arn is None:
69 | print("Creating new PEK")
70 | key_arn = controlplane_client.create_key(Exportable=True,
71 | KeyAttributes={
72 | "KeyAlgorithm": "TDES_3KEY",
73 | "KeyUsage": "TR31_P0_PIN_ENCRYPTION_KEY",
74 | "KeyClass": "SYMMETRIC_KEY",
75 | "KeyModesOfUse": {"Encrypt": True, "Decrypt": True, "Wrap": True,
76 | "Unwrap": True}
77 | },
78 | Tags=[{"Key": TAG_KEY, "Value": "1"}])['Key']['KeyArn']
79 |
80 | create_alias(alias, key_arn)
81 | return key_arn
82 |
83 |
84 | def apc_generate_ecdh():
85 | alias = "ecdh"
86 | key_arn = get_key_by_alias(alias)
87 | if key_arn is None:
88 | print("Creating new ECDH Key")
89 | key_arn = controlplane_client.create_key(Enabled=True, Exportable=True, KeyAttributes={
90 | 'KeyAlgorithm': 'ECC_NIST_P256',
91 | 'KeyClass': 'ASYMMETRIC_KEY_PAIR',
92 | 'KeyModesOfUse': {
93 | 'DeriveKey': True
94 | },
95 | 'KeyUsage': 'TR31_K3_ASYMMETRIC_KEY_FOR_KEY_AGREEMENT'
96 | },
97 | Tags=[{"Key": TAG_KEY, "Value": "1"}])['Key']['KeyArn']
98 | create_alias(alias, key_arn)
99 | return key_arn
100 |
101 |
102 | def create_certificate_authority():
103 | # create Certificate Authority for short-lived certificates
104 | ca_arn = private_ca.create_certificate_authority(
105 | CertificateAuthorityConfiguration={
106 | 'KeyAlgorithm': 'EC_prime256v1',
107 | 'SigningAlgorithm': 'SHA256WITHECDSA',
108 | 'Subject': {
109 | 'CommonName': 'pindemo',
110 | },
111 | },
112 | CertificateAuthorityType='ROOT',
113 | UsageMode='SHORT_LIVED_CERTIFICATE'
114 | )['CertificateAuthorityArn']
115 |
116 | state = "CREATING"
117 | while state == "CREATING":
118 | time.sleep(1)
119 | state = private_ca.describe_certificate_authority(CertificateAuthorityArn=ca_arn)['CertificateAuthority'][
120 | 'Status']
121 | print(state)
122 | return ca_arn
123 |
124 |
125 | def create_private_ca():
126 | print("Creating AWS Private CA")
127 | cert_authority_arn = create_certificate_authority()
128 | print("Newly created Private CA ARN: %s" % cert_authority_arn)
129 | # add tag to CA
130 | private_ca.tag_certificate_authority(
131 | CertificateAuthorityArn=cert_authority_arn,
132 | Tags=[
133 | {
134 | 'Key': TAG_KEY,
135 | 'Value': '1'
136 | },
137 | ]
138 | )
139 | print("Getting root CA CSR")
140 | csr = private_ca.get_certificate_authority_csr(CertificateAuthorityArn=cert_authority_arn)['Csr']
141 | print("self-signing root CA CSR")
142 | certificate, chain = CryptoUtils.sign_with_private_ca(cert_authority_arn, csr, {
143 | 'Value': 10,
144 | 'Type': 'YEARS'
145 | }, template='arn:aws:acm-pca:::template/RootCACertificate/V1')
146 | print("Importing signed certificate as ROOT")
147 | private_ca.import_certificate_authority_certificate(CertificateAuthorityArn=cert_authority_arn,
148 | Certificate=certificate)
149 | print("CA Setup complete")
150 | return cert_authority_arn
151 |
152 |
153 | def find_or_create_private_ca():
154 | # find existing CA
155 | for ca in private_ca.list_certificate_authorities()['CertificateAuthorities']:
156 | if ca['Status'] == 'ACTIVE':
157 | # get ca tags
158 | tags = private_ca.list_tags(CertificateAuthorityArn=ca['Arn'])
159 | for tag in tags['Tags']:
160 | if tag['Key'] == TAG_KEY:
161 | # if tag is present, use this CA
162 | print("Found existing CA: %s" % ca['Arn'])
163 | return ca['Arn']
164 | return create_private_ca()
165 |
166 |
167 | def setup():
168 | ca_arn = find_or_create_private_ca()
169 | ca_certificate = private_ca.get_certificate_authority_certificate(CertificateAuthorityArn=ca_arn)['Certificate']
170 | ca_key_arn = import_ca_key_to_apc(ca_certificate)
171 | pgk_arn = apc_generate_pgk()
172 | pek_arn = apc_generate_pek()
173 | ecdh_arn = apc_generate_ecdh()
174 |
175 | return ca_arn, ca_key_arn, pgk_arn, pek_arn, ecdh_arn
176 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/main.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('./')
3 | from ecdh.backend import Backend
4 | from ecdh.client import Client
5 | from ecdh.setup import setup
6 |
7 |
8 | ca_arn, apc_client_ca_key_arn, apc_pgk_arn, apc_pek_arn, apc_ecdsa_key_arn = setup()
9 |
10 | backend = Backend(ca_arn, apc_pek_arn, apc_client_ca_key_arn, apc_pgk_arn, apc_ecdsa_key_arn)
11 |
12 | client = Client()
13 |
14 | pan = "1234567889012345"
15 | print("FLOW #1: Generating a random PIN for CC and revealing it for the user to memorize")
16 | new_pin = client.pin_reset(pan, backend)
17 | new_pin_pvv = backend.pvv
18 | print("New Pin is %s, PVV is %s" % (new_pin, new_pin_pvv))
19 |
20 |
21 | set_pin = "1234"
22 | print("FLOW #2: Setting an arbitrary PIN %s obtaining it's PVV and encrypted pinblock" % 1234)
23 | client.set_pin(set_pin, pan, backend)
24 | set_pin_pvv = backend.pvv
25 | encrypted_pinblock = backend.tmp_pek_pinblock
26 | print("Encrypted pinblock is %s, PVV is %s" % (encrypted_pinblock, set_pin_pvv))
27 |
28 |
29 | print("FLOW #3: Revealing the PIN")
30 | revealed_pin = client.pin_reveal(encrypted_pinblock, pan, backend)
31 | print("Revealed PIN %s" % revealed_pin)
32 | if revealed_pin == set_pin:
33 | print("OK: Revealed PIN == SET PIN")
34 | else:
35 | print("ERROR")
36 |
37 | print("Please remember to execute tear_down.py if you want to remove the assets created. AWS Private CA has a cost of US$ 50 per month (prorated), and each APC key is worth US$ 1/month (prorated)")
38 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/payment_crypto/tear_down.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | sys.path.append('./')
4 | import boto3
5 | from ecdh.setup import KEY_ALIAS_PREFIX, TAG_KEY
6 |
7 | controlplane_client = boto3.client("payment-cryptography")
8 | private_ca = boto3.client("acm-pca")
9 |
10 | # Delete created certificate authority
11 | for ca in private_ca.list_certificate_authorities()['CertificateAuthorities']:
12 | if ca['Status'] != 'ACTIVE':
13 | continue
14 | tags = private_ca.list_tags(CertificateAuthorityArn=ca['Arn'])
15 | for tag in tags['Tags']:
16 | if tag['Key'] == TAG_KEY:
17 | print("Deleting %s" % ca['Arn'])
18 | # disable CA to delete
19 | private_ca.update_certificate_authority(CertificateAuthorityArn=ca['Arn'], Status='DISABLED')
20 | # Delete
21 | private_ca.delete_certificate_authority(CertificateAuthorityArn=ca['Arn'], PermanentDeletionTimeInDays=7)
22 |
23 | # Delete keys
24 | keys = controlplane_client.list_keys(KeyState='CREATE_COMPLETE')
25 | for key in keys['Keys']:
26 | print(key)
27 | tags = controlplane_client.list_tags_for_resource(ResourceArn=key['KeyArn'])
28 | for tag in tags['Tags']:
29 | if tag['Key'] == TAG_KEY:
30 | print("Deleting %s" % key['KeyArn'])
31 | controlplane_client.delete_key(KeyIdentifier=key['KeyArn'])
32 | # controlplane_client.delete_key(KeyIdentifier=key['KeyArn'])
33 |
34 | # Delete aliases
35 | aliases = controlplane_client.list_aliases()
36 | for alias in aliases['Aliases']:
37 | print(alias)
38 | if not alias['AliasName'].startswith("alias/%s" % KEY_ALIAS_PREFIX):
39 | continue
40 | print("Deleting %s" % alias['AliasName'])
41 | controlplane_client.delete_alias(AliasName=alias['AliasName'])
42 |
--------------------------------------------------------------------------------
/python_sdk_example/ecdh_flows/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3==1.35.54
2 | cryptography==44.0.1
3 | psec==1.3.0
4 |
--------------------------------------------------------------------------------