├── .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 | ![Atalla TR-34 Flow](./assets/atalla-apc-tr34-key-exchange-sequence-diagram%20-%20Key%20Exchange.png) 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 | ![TR-34 Flow - DUKPT](../../flows/apc-key-exchange-sequence-diagram-TR34.png) 7 | 8 | #### **TR-31 - Symmetric key exchange protocol to setp working keys (PEK, PVK, PGK etc)** 9 | ![TR-34 Flow - DUKPT](../../flows/apc-key-exchange-sequence-diagram-TR31.png) 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 | ![](./architecture.png) 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 |  timings 2 | ────────────────────────────────────────── 3 |  disk i/o  141.814µs 4 |  parsing  6.776173ms 5 |  adaptation  656.735µs 6 |  checks  7.231684ms 7 |  total  14.806406ms 8 |  9 |  counts 10 | ────────────────────────────────────────── 11 |  modules downloaded  0 12 |  modules processed  4 13 |  blocks processed  53 14 |  files read  13 15 |  16 |  results 17 | ────────────────────────────────────────── 18 |  passed  30 19 |  ignored  12 20 |  critical  0 21 |  high  0 22 |  medium  0 23 |  low  0 24 |  25 |  26 | No problems detected! 27 | 28 |  -------------------------------------------------------------------------------- /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 | ![PIN Reset](images/PIN-Reset.png?raw=true "PIN Reset") 13 | 14 | ### Select PIN 15 | ![PIN Select](images/PIN-Select.png?raw=true "PIN Select") 16 | 17 | ### Reveal PIN 18 | ![PIN Reveal](images/PIN-Reveal.png?raw=true "PIN Reveal") 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 | --------------------------------------------------------------------------------