├── .bazelrc ├── .bazelversion ├── BUILD.bazel ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE.bzlmod ├── docs ├── CONTRIBUTING.md └── SECURITY.md ├── kokoro ├── create_github_release_branch.sh ├── create_github_release_tag.sh ├── gcp_ubuntu │ ├── bazel │ │ └── run_tests.sh │ └── maven │ │ └── publish_snapshots.sh ├── macos_external │ └── bazel │ │ └── run_tests.sh ├── release_maven.sh └── testutils │ ├── BUILD.bazel │ ├── docker_execute.sh │ ├── github_release_util.sh │ ├── github_release_util_test.sh │ ├── java_test_container_images.sh │ ├── run_bazel_tests.sh │ ├── test_utils.sh │ └── update_android_sdk.sh ├── maven ├── BUILD.bazel ├── maven_deploy_library.sh ├── maven_deploy_library_test.sh ├── settings.xml ├── tink-java-apps-paymentmethodtoken.pom.xml ├── tink-java-apps-rewardedads.pom.xml └── tink-java-apps-webpush.pom.xml ├── paymentmethodtoken ├── BUILD.bazel └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── crypto │ │ └── tink │ │ └── apps │ │ └── paymentmethodtoken │ │ ├── BUILD.bazel │ │ ├── GooglePaymentsPublicKeysManager.java │ │ ├── JwtKeyConverter.java │ │ ├── KeysDownloader.java │ │ ├── PaymentMethodTokenConstants.java │ │ ├── PaymentMethodTokenHybridDecrypt.java │ │ ├── PaymentMethodTokenHybridEncrypt.java │ │ ├── PaymentMethodTokenRecipient.java │ │ ├── PaymentMethodTokenRecipientKem.java │ │ ├── PaymentMethodTokenRecipientKeyGen.java │ │ ├── PaymentMethodTokenSender.java │ │ ├── PaymentMethodTokenUtil.java │ │ └── SenderIntermediateCertFactory.java │ └── test │ ├── BUILD.bazel │ └── java │ └── com │ └── google │ └── crypto │ └── tink │ └── apps │ └── paymentmethodtoken │ ├── ExampleTest.java │ ├── GooglePaymentsPublicKeyManagerTest.java │ ├── JwtKeyConverterTest.java │ ├── KeysDownloaderTest.java │ ├── PaymentMethodJsonEncodingTest.java │ ├── PaymentMethodTokenHybridDecryptTest.java │ ├── PaymentMethodTokenHybridEncryptTest.java │ ├── PaymentMethodTokenRecipientTest.java │ ├── PaymentMethodTokenSenderTest.java │ └── SenderIntermediateCertFactoryTest.java ├── rewardedads ├── BUILD.bazel └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── crypto │ │ └── tink │ │ └── apps │ │ └── rewardedads │ │ ├── BUILD.bazel │ │ ├── KeysDownloader.java │ │ └── RewardedAdsVerifier.java │ └── test │ ├── BUILD.bazel │ └── java │ └── com │ └── google │ └── crypto │ └── tink │ └── apps │ └── rewardedads │ ├── KeysDownloaderTest.java │ └── RewardedAdsVerifierTest.java ├── tools ├── BUILD.bazel ├── gen_java_test_rules.bzl ├── gen_maven_jar_rules.bzl ├── jar_jar.bzl ├── java_single_jar.bzl └── javadoc.bzl └── webpush ├── BUILD.bazel └── src ├── main └── java │ └── com │ └── google │ └── crypto │ └── tink │ └── apps │ └── webpush │ ├── BUILD.bazel │ ├── WebPushConstants.java │ ├── WebPushHybridDecrypt.java │ ├── WebPushHybridEncrypt.java │ └── WebPushUtil.java └── test ├── BUILD.bazel └── java └── com └── google └── crypto └── tink └── apps └── webpush ├── WebPushHybridDecryptTest.java └── WebPushHybridEncryptTest.java /.bazelrc: -------------------------------------------------------------------------------- 1 | build --cxxopt='-std=c++14' --host_cxxopt='-std=c++14' 2 | build --java_language_version=8 3 | build --java_runtime_version=local_jdk 4 | 5 | # Silence all C/C++ warnings in external code. 6 | # 7 | # Note that this will not silence warnings from external headers included 8 | # in project code. 9 | build --per_file_copt=external/.*@-w 10 | build --host_per_file_copt=external/.*@-w 11 | 12 | # Needed by https://github.com/bazelbuild/rules_android?tab=readme-ov-file#android-support-in-bazel. 13 | common --experimental_enable_android_migration_apis 14 | common --experimental_google_legacy_api 15 | common --android_databinding_use_v3_4_args 16 | common --experimental_android_databinding_v2 17 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.2.1 2 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | licenses(["notice"]) 4 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | """Tink Java Apps module.""" 2 | 3 | module( 4 | name = "tink_java_apps", 5 | version = "1.12.2", 6 | ) 7 | 8 | bazel_dep( 9 | name = "platforms", 10 | version = "0.0.10", 11 | ) 12 | 13 | bazel_dep( 14 | name = "bazel_skylib", 15 | version = "1.7.1", 16 | ) 17 | 18 | bazel_dep( 19 | name = "rules_java", 20 | version = "7.6.5", 21 | ) 22 | 23 | bazel_dep( 24 | name = "rules_jvm_external", 25 | version = "6.1", 26 | ) 27 | 28 | # Overriding to a commit that doesn't check for Java version. This is to avoid version parsing [1] 29 | # which may fail [2]. Note that since [1] rules_jvm_external requires Java 11. 30 | # 31 | # [1] https://github.com/bazelbuild/rules_jvm_external/commit/4f56f7cec2fa3a47e34d48b8f6293785cfad7e3a 32 | # [2] https://github.com/bazelbuild/rules_jvm_external/issues/1115 33 | # Commit from Apr 29, 2024. 34 | git_override( 35 | module_name = "rules_jvm_external", 36 | commit = "4f56f7cec2fa3a47e34d48b8f6293785cfad7e3a", 37 | remote = "https://github.com/bazelbuild/rules_jvm_external", 38 | ) 39 | 40 | # This is needed to overwrite rules_jvm_external's toolchain to allow running with `root` as user. 41 | bazel_dep( 42 | name = "rules_python", 43 | version = "0.33.2", 44 | ) 45 | 46 | bazel_dep( 47 | name = "rules_android", 48 | version = "0.2.0", 49 | ) 50 | 51 | # Overriding as per 52 | # https://github.com/bazelbuild/rules_android/blob/eb728f67cae8392360338d07d59835db76bee2c2/README.md. 53 | # Commit from May 22, 2024. 54 | RULES_ANDROID_COMMIT = "b2e8bf74e84b1bb20102e44d34620d4ad9ff3dd8" 55 | 56 | git_override( 57 | module_name = "rules_android", 58 | commit = RULES_ANDROID_COMMIT, 59 | remote = "https://github.com/bazelbuild/rules_android", 60 | ) 61 | 62 | register_toolchains( 63 | "@rules_android//toolchains/android:android_default_toolchain", 64 | "@rules_android//toolchains/android_sdk:android_sdk_tools", 65 | ) 66 | 67 | python = use_extension("@rules_python//python/extensions:python.bzl", "python") 68 | 69 | python.toolchain( 70 | configure_coverage_tool = True, 71 | ignore_root_user_error = True, 72 | # Only set when you have mulitple toolchain versions. 73 | is_default = True, 74 | python_version = "3.11", 75 | ) 76 | 77 | maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") 78 | 79 | maven.install( 80 | artifacts = [ 81 | "androidx.annotation:annotation:1.8.2", 82 | "com.google.code.findbugs:jsr305:3.0.2", 83 | "com.google.crypto.tink:tink:1.16.0", 84 | "com.google.errorprone:error_prone_annotations:2.23.0", 85 | "com.google.http-client:google-http-client:1.46.3", 86 | "org.ow2.asm:asm-commons:9.7", 87 | "org.pantsbuild:jarjar:1.7.2", 88 | ], 89 | repositories = [ 90 | "https://maven.google.com", # For androidx.annotation:annotation:1.5.0. 91 | "https://repo1.maven.org/maven2", 92 | ], 93 | ) 94 | 95 | use_repo(maven, "maven") 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tink Java Apps 2 | 3 | 4 | 5 | [tink_java_apps_bazel_badge_gcp_ubuntu]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-java-apps-bazel-gcp-ubuntu.svg 6 | [tink_java_apps_maven_badge_gcp_ubuntu]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-java-apps-maven-gcp-ubuntu.svg 7 | 8 | 9 | 10 | [tink_java_apps_bazel_badge_macos]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-java-apps-bazel-macos-external.svg 11 | 12 | **Test** | **GCP Ubuntu** | **MacOS** 13 | ------------ | -------------------------------------------------------------- | --------- 14 | Tink (Bazel) | [![Bazel_GcpUbuntu][tink_java_apps_bazel_badge_gcp_ubuntu]](#) | [![Bazel_MacOs][tink_java_apps_bazel_badge_macos]](#) 15 | Maven | [![Maven_GcpUbuntu][tink_java_apps_maven_badge_gcp_ubuntu]](#) | N/A 16 | 17 | This repository contains extensions and applications of the 18 | [Tink Java](https://github.com/tink-crypto/tink-java) library: 19 | 20 | * `apps-paymentmethodtoken`: An implementation of 21 | [Google Payment Method Token](https://developers.google.com/pay/api/payment-data-cryptography) 22 | * `apps-rewardedads`: An implementation of the verifier side of 23 | [Server-Side Verification of Google AdMob Rewarded Ads](https://developers.google.com/admob/android/ssv) 24 | * `apps-webpush`: An implementation of 25 | [RFC 8291 - Message Encryption for Web Push](https://www.rfc-editor.org/rfc/rfc8291) 26 | 27 | The latest version of these applications is 1.12.2. 28 | 29 | The official Tink documentation is available at 30 | https://developers.google.com/tink. 31 | 32 | ### apps-paymentmethodtoken 33 | 34 | You can add this library as a Maven dependency: 35 | 36 | ```xml 37 | 38 | com.google.crypto.tink 39 | apps-paymentmethodtoken 40 | 1.12.2 41 | 42 | ``` 43 | 44 | The Javadoc for the latest release can be found 45 | [here](https://tink-crypto.github.io/tink-java-apps/javadoc/apps-paymentmethodtoken/1.12.2/). 46 | 47 | ### apps-rewardedads 48 | 49 | You can add this library as a Maven dependency: 50 | 51 | ```xml 52 | 53 | com.google.crypto.tink 54 | apps-rewardedads 55 | 1.12.2 56 | 57 | ``` 58 | 59 | The Javadoc for the latest release can be found 60 | [here](https://tink-crypto.github.io/tink-java-apps/javadoc/apps-rewardedads/1.12.2/). 61 | 62 | ### apps-webpush 63 | 64 | You can add this library as a Maven dependency: 65 | 66 | ```xml 67 | 68 | com.google.crypto.tink 69 | apps-webpush 70 | 1.12.2 71 | 72 | ``` 73 | 74 | The Javadoc for the latest release can be found 75 | [here](https://tink-crypto.github.io/tink-java-apps/javadoc/apps-webpush/1.12.2/). 76 | 77 | You can encrypt a plaintext as follows: 78 | 79 | ```java 80 | import com.google.crypto.tink.HybridEncrypt; 81 | import java.security.interfaces.ECPublicKey; 82 | 83 | ECPublicKey reicipientPublicKey = ...; 84 | byte[] authSecret = ...; 85 | HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder() 86 | .withAuthSecret(authSecret) 87 | .withRecipientPublicKey(recipientPublicKey) 88 | .build(); 89 | byte[] plaintext = ...; 90 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo, must be null */); 91 | ``` 92 | 93 | To decrypt: 94 | 95 | ```java 96 | import com.google.crypto.tink.HybridDecrypt; 97 | import java.security.interfaces.ECPrivateKey; 98 | import java.security.interfaces.ECPublicKey; 99 | 100 | ECPrivateKey recipientPrivateKey = ...; 101 | ECPublicKey recipientPublicKey = ...; 102 | HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder() 103 | .withAuthSecret(authSecret) 104 | .withRecipientPublicKey(recipientPublicKey) 105 | .withRecipientPrivateKey(recipientPrivateKey) 106 | .build(); 107 | byte[] ciphertext = ...; 108 | byte[] plaintext = hybridDecrypt.decrypt(ciphertext, /* contextInfo, must be null */); 109 | ``` 110 | 111 | ## Contact and mailing list 112 | 113 | If you want to contribute, please read [CONTRIBUTING](docs/CONTRIBUTING.md) and 114 | send us pull requests. You can also report bugs or file feature requests. 115 | 116 | If you'd like to talk to the developers or get notified about major product 117 | updates, you may want to subscribe to our 118 | [mailing list](https://groups.google.com/forum/#!forum/tink-users). 119 | 120 | ## Maintainers 121 | 122 | Tink is maintained by (A-Z): 123 | 124 | - Moreno Ambrosin 125 | - Taymon Beal 126 | - William Conner 127 | - Thomas Holenstein 128 | - Stefan Kölbl 129 | - Charles Lee 130 | - Cindy Lin 131 | - Fernando Lobato Meeser 132 | - Ioana Nedelcu 133 | - Sophie Schmieg 134 | - Elizaveta Tretiakova 135 | - Jürg Wullschleger 136 | 137 | Alumni: 138 | 139 | - Haris Andrianakis 140 | - Daniel Bleichenbacher 141 | - Tanuj Dhir 142 | - Thai Duong 143 | - Atul Luykx 144 | - Rafael Misoczki 145 | - Quan Nguyen 146 | - Bartosz Przydatek 147 | - Enzo Puig 148 | - Laurent Simon 149 | - Veronika Slívová 150 | - Paula Vidas 151 | -------------------------------------------------------------------------------- /WORKSPACE.bzlmod: -------------------------------------------------------------------------------- 1 | load("@rules_android//rules:rules.bzl", "android_sdk_repository") 2 | android_sdk_repository( 3 | name = "androidsdk", 4 | ) 5 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Please see the 4 | [developer documentation](https://developers.google.com/tink/contributing) on 5 | how to contribute to Tink. 6 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | To report a security issue, please use http://g.co/vulnz. We use 2 | http://g.co/vulnz for our intake and coordination, and disclose vulnerabilities 3 | using GitHub Security Advisory. The Google Security Team will 4 | respond within 5 working days of your report on g.co/vulnz. 5 | -------------------------------------------------------------------------------- /kokoro/create_github_release_branch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | set -euo pipefail 18 | 19 | # Fail if RELEASE_VERSION is not set. 20 | if [[ -z "${RELEASE_VERSION:-}" ]]; then 21 | echo "RELEASE_VERSION must be set" >&2 22 | exit 1 23 | fi 24 | 25 | IS_KOKORO="false" 26 | if [[ -n "${KOKORO_ARTIFACTS_DIR:-}" ]]; then 27 | IS_KOKORO="true" 28 | fi 29 | readonly IS_KOKORO 30 | 31 | # If not defined, default to /tmp. 32 | : "${TMPDIR:="/tmp"}" 33 | 34 | # WARNING: Setting this environment varialble to "true" will cause this script 35 | # to actually perform a release. 36 | : "${DO_MAKE_RELEASE:="false"}" 37 | 38 | if [[ ! "${DO_MAKE_RELEASE}" =~ ^(false|true)$ ]]; then 39 | echo "DO_MAKE_RELEASE must be either \"true\" or \"false\"" >&2 40 | exit 1 41 | fi 42 | 43 | GITHUB_RELEASE_UTIL_OPTS=() 44 | if [[ "${IS_KOKORO}" == "true" ]] ; then 45 | # Note: KOKORO_GIT_COMMIT and GITHUB_ACCESS_TOKEN are populated by Kokoro. 46 | GITHUB_RELEASE_UTIL_OPTS+=( 47 | -c "${KOKORO_GIT_COMMIT}" 48 | -t "${GITHUB_ACCESS_TOKEN}" 49 | ) 50 | readonly TINK_BASE_DIR="$(echo "${KOKORO_ARTIFACTS_DIR}"/git*)" 51 | cd "${TINK_BASE_DIR}/tink_java_apps" 52 | fi 53 | if [[ "${DO_MAKE_RELEASE}" == "true" ]]; then 54 | GITHUB_RELEASE_UTIL_OPTS+=( -r ) 55 | fi 56 | readonly GITHUB_RELEASE_UTIL_OPTS 57 | 58 | # If running on Kokoro, TMPDIR is populated with the tmp folder. 59 | readonly TMP_FOLDER="$(mktemp -d "${TMPDIR}/release_XXXXXX")" 60 | readonly RELEASE_UTIL_SCRIPT="$(pwd)/kokoro/testutils/github_release_util.sh" 61 | if [[ ! -f "${RELEASE_UTIL_SCRIPT}" ]]; then 62 | echo "${RELEASE_UTIL_SCRIPT} not found." >&2 63 | echo "Make sure you run this script from the root of tink-java-apps." >&2 64 | exit 1 65 | fi 66 | 67 | pushd "${TMP_FOLDER}" 68 | "${RELEASE_UTIL_SCRIPT}" "${GITHUB_RELEASE_UTIL_OPTS[@]}" create_branch \ 69 | "${RELEASE_VERSION}" tink-java-apps 70 | popd 71 | -------------------------------------------------------------------------------- /kokoro/create_github_release_tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | set -euo pipefail 18 | 19 | # Fail if RELEASE_VERSION is not set. 20 | if [[ -z "${RELEASE_VERSION:-}" ]]; then 21 | echo "RELEASE_VERSION must be set" >&2 22 | exit 1 23 | fi 24 | 25 | IS_KOKORO="false" 26 | if [[ -n "${KOKORO_ARTIFACTS_DIR:-}" ]]; then 27 | IS_KOKORO="true" 28 | fi 29 | readonly IS_KOKORO 30 | 31 | # If not defined, default to /tmp. 32 | : "${TMPDIR:="/tmp"}" 33 | 34 | # WARNING: Setting this environment varialble to "true" will cause this script 35 | # to actually perform a release. 36 | : "${DO_MAKE_RELEASE:="false"}" 37 | 38 | if [[ ! "${DO_MAKE_RELEASE}" =~ ^(false|true)$ ]]; then 39 | echo "DO_MAKE_RELEASE must be either \"true\" or \"false\"" >&2 40 | exit 1 41 | fi 42 | 43 | GITHUB_RELEASE_UTIL_OPTS=() 44 | if [[ "${IS_KOKORO}" == "true" ]] ; then 45 | # Note: KOKORO_GIT_COMMIT and GITHUB_ACCESS_TOKEN are populated by Kokoro. 46 | GITHUB_RELEASE_UTIL_OPTS+=( 47 | -c "${KOKORO_GIT_COMMIT}" 48 | -t "${GITHUB_ACCESS_TOKEN}" 49 | ) 50 | readonly TINK_BASE_DIR="$(echo "${KOKORO_ARTIFACTS_DIR}"/git*)" 51 | cd "${TINK_BASE_DIR}/tink_java_apps" 52 | fi 53 | if [[ "${DO_MAKE_RELEASE}" == "true" ]]; then 54 | GITHUB_RELEASE_UTIL_OPTS+=( -r ) 55 | fi 56 | readonly GITHUB_RELEASE_UTIL_OPTS 57 | 58 | # If running on Kokoro, TMPDIR is populated with the tmp folder. 59 | readonly TMP_FOLDER="$(mktemp -d "${TMPDIR}/release_XXXXXX")" 60 | readonly RELEASE_UTIL_SCRIPT="$(pwd)/kokoro/testutils/github_release_util.sh" 61 | if [[ ! -f "${RELEASE_UTIL_SCRIPT}" ]]; then 62 | echo "${RELEASE_UTIL_SCRIPT} not found." >&2 63 | echo "Make sure you run this script from the root of tink-java-apps." >&2 64 | exit 1 65 | fi 66 | 67 | pushd "${TMP_FOLDER}" 68 | "${RELEASE_UTIL_SCRIPT}" "${GITHUB_RELEASE_UTIL_OPTS[@]}" create_tag \ 69 | "${RELEASE_VERSION}" tink-java-apps 70 | popd 71 | -------------------------------------------------------------------------------- /kokoro/gcp_ubuntu/bazel/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # Builds and tests tink-java-apps using Bazel. 18 | # 19 | # The behavior of this script can be modified using the following optional env 20 | # variables: 21 | # 22 | # - CONTAINER_IMAGE (unset by default): By default when run locally this script 23 | # executes tests directly on the host. The CONTAINER_IMAGE variable can be set 24 | # to execute tests in a custom container image for local testing. E.g.: 25 | # 26 | # CONTAINER_IMAGE="us-docker.pkg.dev/tink-test-infrastructure/tink-ci-images/linux-tink-java-base:latest" \ 27 | # sh ./kokoro/gcp_ubuntu/bazel/run_tests.sh 28 | set -eEuo pipefail 29 | 30 | RUN_COMMAND_ARGS=() 31 | if [[ -n "${KOKORO_ARTIFACTS_DIR:-}" ]] ; then 32 | readonly TINK_BASE_DIR="$(echo "${KOKORO_ARTIFACTS_DIR}"/git*)" 33 | cd "${TINK_BASE_DIR}/tink_java_apps" 34 | source ./kokoro/testutils/java_test_container_images.sh 35 | CONTAINER_IMAGE="${TINK_JAVA_BASE_IMAGE}" 36 | RUN_COMMAND_ARGS+=( -k "${TINK_GCR_SERVICE_KEY}" ) 37 | fi 38 | readonly CONTAINER_IMAGE 39 | 40 | if [[ -n "${CONTAINER_IMAGE:-}" ]]; then 41 | RUN_COMMAND_ARGS+=( -c "${CONTAINER_IMAGE}" ) 42 | fi 43 | readonly RUN_COMMAND_ARGS 44 | 45 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 46 | ./kokoro/testutils/run_bazel_tests.sh . 47 | -------------------------------------------------------------------------------- /kokoro/gcp_ubuntu/maven/publish_snapshots.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # Builds and publishes tink-java-apps Maven snapshots. 18 | # 19 | # The behavior of this script can be modified using the following optional env 20 | # variables: 21 | # 22 | # - CONTAINER_IMAGE (unset by default): By default when run locally this script 23 | # executes tests directly on the host. The CONTAINER_IMAGE variable can be set 24 | # to execute tests in a custom container image for local testing. E.g.: 25 | # 26 | # CONTAINER_IMAGE="us-docker.pkg.dev/tink-test-infrastructure/tink-ci-images/linux-tink-java-base:latest" \ 27 | # sh ./kokoro/gcp_ubuntu/maven/publish_snapshots.sh 28 | set -eEuo pipefail 29 | 30 | IS_KOKORO="false" 31 | if [[ -n "${KOKORO_ARTIFACTS_DIR:-}" ]] ; then 32 | IS_KOKORO="true" 33 | fi 34 | readonly IS_KOKORO 35 | 36 | RUN_COMMAND_ARGS=() 37 | if [[ "${IS_KOKORO}" == "true" ]] ; then 38 | readonly TINK_BASE_DIR="$(echo "${KOKORO_ARTIFACTS_DIR}"/git*)" 39 | cd "${TINK_BASE_DIR}/tink_java_apps" 40 | source ./kokoro/testutils/java_test_container_images.sh 41 | CONTAINER_IMAGE="${TINK_JAVA_BASE_IMAGE}" 42 | RUN_COMMAND_ARGS+=( -k "${TINK_GCR_SERVICE_KEY}" ) 43 | fi 44 | readonly CONTAINER_IMAGE 45 | 46 | if [[ -n "${CONTAINER_IMAGE:-}" ]]; then 47 | RUN_COMMAND_ARGS+=( -c "${CONTAINER_IMAGE}" ) 48 | fi 49 | 50 | # TODO(b/263465812): Add test example app for testing the Maven snapshots. 51 | 52 | readonly GITHUB_JOB_NAME="tink/github/java_apps/gcp_ubuntu/maven/continuous" 53 | 54 | if [[ "${IS_KOKORO}" == "true" \ 55 | && "${KOKORO_JOB_NAME}" == "${GITHUB_JOB_NAME}" ]] ; then 56 | # GITHUB_ACCESS_TOKEN is populated by Kokoro. 57 | readonly GIT_CREDENTIALS="ise-crypto:${GITHUB_ACCESS_TOKEN}" 58 | readonly GITHUB_URL="https://${GIT_CREDENTIALS}@github.com/tink-crypto/tink-java-apps.git" 59 | 60 | # Share the required env variables with the container to allow publishing the 61 | # snapshot on Sonatype. 62 | cat < env_variables.txt 63 | SONATYPE_USERNAME 64 | SONATYPE_PASSWORD 65 | EOF 66 | RUN_COMMAND_ARGS+=( -e env_variables.txt ) 67 | 68 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 69 | ./maven/maven_deploy_library.sh -u "${GITHUB_URL}" \ 70 | -n paymentmethodtoken/maven snapshot apps-paymentmethodtoken \ 71 | maven/tink-java-apps-paymentmethodtoken.pom.xml HEAD 72 | 73 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 74 | ./maven/maven_deploy_library.sh -u "${GITHUB_URL}" \ 75 | -n rewardedads/maven snapshot apps-rewardedads \ 76 | maven/tink-java-apps-rewardedads.pom.xml HEAD 77 | 78 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 79 | ./maven/maven_deploy_library.sh -u "${GITHUB_URL}" \ 80 | -n webpush/maven snapshot apps-webpush \ 81 | maven/tink-java-apps-webpush.pom.xml HEAD 82 | fi 83 | -------------------------------------------------------------------------------- /kokoro/macos_external/bazel/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | set -euo pipefail 18 | 19 | if [[ -n "${KOKORO_ROOT:-}" ]] ; then 20 | readonly TINK_BASE_DIR="$(echo "${KOKORO_ARTIFACTS_DIR}"/git*)" 21 | cd "${TINK_BASE_DIR}/tink_java_apps" 22 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11-latest/Contents/Home 23 | export XCODE_VERSION="14.1" 24 | export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" 25 | fi 26 | 27 | source ./kokoro/testutils/update_android_sdk.sh 28 | # Skip Maven packages generation. 29 | # TODO(b/342560474): Test all targets once Javadoc generation is fixed on Kokoro 30 | # macOS. 31 | ./kokoro/testutils/run_bazel_tests.sh -m . //paymentmethodtoken/src/... \ 32 | //rewardedads/src/... //webpush/src/... 33 | -------------------------------------------------------------------------------- /kokoro/release_maven.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # The behavior of this script can be modified using the following optional env 18 | # variables: 19 | # 20 | # - CONTAINER_IMAGE (unset by default): By default when run locally this script 21 | # executes tests directly on the host. The CONTAINER_IMAGE variable can be set 22 | # to execute tests in a custom container image for local testing. E.g.: 23 | # 24 | # CONTAINER_IMAGE="us-docker.pkg.dev/tink-test-infrastructure/tink-ci-images/linux-tink-java-base:latest" \ 25 | # sh ./kokoro/release_maven.sh 26 | 27 | set -euo pipefail 28 | 29 | # Fail if RELEASE_VERSION is not set. 30 | if [[ -z "${RELEASE_VERSION:-}" ]]; then 31 | echo "RELEASE_VERSION must be set" >&2 32 | exit 1 33 | fi 34 | 35 | readonly TINK_JAVA_APPS_GITHUB_URL="github.com/tink-crypto/tink-java-apps" 36 | IS_KOKORO="false" 37 | if [[ -n "${KOKORO_ARTIFACTS_DIR:-}" ]]; then 38 | IS_KOKORO="true" 39 | fi 40 | readonly IS_KOKORO 41 | 42 | # WARNING: Setting this environment varialble to "true" will cause this script 43 | # to actually perform a release. 44 | : "${DO_MAKE_RELEASE:="false"}" 45 | 46 | if [[ ! "${DO_MAKE_RELEASE}" =~ ^(false|true)$ ]]; then 47 | echo "DO_MAKE_RELEASE must be either \"true\" or \"false\"" >&2 48 | exit 1 49 | fi 50 | 51 | ####################################### 52 | # Create a Maven release on Sonatype. 53 | # 54 | # Globals: 55 | # GITHUB_ACCESS_TOKEN (optional from Kokoro) 56 | # IS_KOKORO 57 | # RELEASE_VERSION 58 | # TINK_JAVA_APPS_GITHUB_URL 59 | # 60 | ####################################### 61 | create_maven_release() { 62 | local gitub_protocol_and_auth="ssh://git" 63 | if [[ "${IS_KOKORO}" == "true" ]] ; then 64 | gitub_protocol_and_auth="https://ise-crypto:${GITHUB_ACCESS_TOKEN}" 65 | source "./kokoro/testutils/java_test_container_images.sh" 66 | CONTAINER_IMAGE="${TINK_JAVA_BASE_IMAGE}" 67 | RUN_COMMAND_ARGS+=( -k "${TINK_GCR_SERVICE_KEY}" ) 68 | fi 69 | readonly gitub_protocol_and_auth 70 | local -r github_url="${gitub_protocol_and_auth}@${TINK_JAVA_APPS_GITHUB_URL}" 71 | readonly CONTAINER_IMAGE 72 | 73 | if [[ -n "${CONTAINER_IMAGE:-}" ]]; then 74 | RUN_COMMAND_ARGS+=( -c "${CONTAINER_IMAGE}" ) 75 | fi 76 | 77 | local maven_deploy_library_options=( -u "${github_url}" ) 78 | if [[ "${DO_MAKE_RELEASE}" == "false" ]]; then 79 | maven_deploy_library_options+=( -d ) 80 | fi 81 | readonly maven_deploy_library_options 82 | 83 | local install_mvn_certificate_cmd="" 84 | 85 | if [[ "${IS_KOKORO}" == "true" && "${DO_MAKE_RELEASE}" == "true" ]]; then 86 | # Copy PGP key and passphrase files where the container can access them. 87 | cp "${KOKORO_KEYSTORE_DIR}/70968_tink_dev_maven_pgp_secret_key" \ 88 | tink_dev_maven_pgp_secret_key 89 | cp "${KOKORO_KEYSTORE_DIR}/70968_tink_dev_maven_pgp_passphrase" \ 90 | tink_dev_maven_pgp_passphrase 91 | cat < install_maven_key.sh 92 | gpg --import --pinentry-mode loopback --passphrase-file \ 93 | ./tink_dev_maven_pgp_passphrase --batch ./tink_dev_maven_pgp_secret_key 94 | EOF 95 | chmod +x install_maven_key.sh 96 | 97 | install_mvn_certificate_cmd="./install_maven_key.sh &&" 98 | fi 99 | 100 | readonly install_mvn_certificate_cmd 101 | 102 | # Share the required env variables with the container to allow publishing the 103 | # snapshot on Sonatype. 104 | cat < env_variables.txt 105 | SONATYPE_USERNAME 106 | SONATYPE_PASSWORD 107 | TINK_DEV_MAVEN_PGP_PASSPHRASE 108 | EOF 109 | RUN_COMMAND_ARGS+=( -e env_variables.txt ) 110 | 111 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 112 | "${install_mvn_certificate_cmd}" \ 113 | ./maven/maven_deploy_library.sh "${maven_deploy_library_options[@]}" \ 114 | -n paymentmethodtoken/maven release apps-paymentmethodtoken \ 115 | maven/tink-java-apps-paymentmethodtoken.pom.xml "${RELEASE_VERSION}" 116 | 117 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 118 | "${install_mvn_certificate_cmd}" \ 119 | ./maven/maven_deploy_library.sh "${maven_deploy_library_options[@]}" \ 120 | -n rewardedads/maven release apps-rewardedads \ 121 | maven/tink-java-apps-rewardedads.pom.xml "${RELEASE_VERSION}" 122 | 123 | ./kokoro/testutils/docker_execute.sh "${RUN_COMMAND_ARGS[@]}" \ 124 | "${install_mvn_certificate_cmd}" \ 125 | ./maven/maven_deploy_library.sh "${maven_deploy_library_options[@]}" \ 126 | -n webpush/maven release apps-webpush \ 127 | maven/tink-java-apps-webpush.pom.xml "${RELEASE_VERSION}" 128 | } 129 | 130 | main() { 131 | if [[ "${IS_KOKORO}" == "true" ]] ; then 132 | readonly TINK_BASE_DIR="$(echo "${KOKORO_ARTIFACTS_DIR}"/git*)" 133 | cd "${TINK_BASE_DIR}/tink_java_apps" 134 | fi 135 | create_maven_release 136 | } 137 | 138 | main "$@" 139 | -------------------------------------------------------------------------------- /kokoro/testutils/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | licenses(["notice"]) 4 | 5 | sh_binary( 6 | name = "test_utils", 7 | srcs = ["test_utils.sh"], 8 | ) 9 | 10 | sh_binary( 11 | name = "github_release_util", 12 | srcs = ["github_release_util.sh"], 13 | ) 14 | 15 | sh_test( 16 | name = "github_release_util_test", 17 | size = "small", 18 | srcs = ["github_release_util_test.sh"], 19 | args = [ 20 | "$(rlocationpath :github_release_util.sh)", 21 | "$(rlocationpath :test_utils)", 22 | ], 23 | data = [ 24 | ":github_release_util.sh", 25 | ":test_utils", 26 | ], 27 | target_compatible_with = select({ 28 | "@platforms//os:windows": ["@platforms//:incompatible"], 29 | "//conditions:default": [], 30 | }), 31 | ) 32 | -------------------------------------------------------------------------------- /kokoro/testutils/docker_execute.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2023 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # Utility script to run a command in a new docker container. 18 | # 19 | # This script must be run from inside the Tink library to run the command for. 20 | # 21 | # NOTE: When running in a new container, this script mounts the parent folder of 22 | # `pwd`. Other dependencies, if any, are assumed to be located there. For 23 | # example, if running tink-py tests, this script assumes: 24 | # - pwd => /path/to/parent/tink-py 25 | # - mount path => /path/to/parent 26 | # - ls /path/to/parent => tink_cc tink_py. 27 | 28 | set -eo pipefail 29 | 30 | usage() { 31 | cat <] [-k ] 33 | -c: [Optional] Container image to run the command on. 34 | -k: [Optional] Service key file path for pulling the image from the Google Artifact Registry (https://cloud.google.com/artifact-registry). 35 | -e: [Optional] File containing a list of environment variables to pass to Docker using --env-file (see https://docs.docker.com/engine/reference/commandline/run/#env). 36 | -m: [Optional] Additional --mount argument to pass to the Docker command (see https://docs.docker.com/reference/cli/docker/container/run/#mount). 37 | -h: Help. Print this usage information. 38 | EOF 39 | exit 1 40 | } 41 | 42 | # Args. 43 | COMMAND= 44 | 45 | # Options. 46 | CONTAINER_IMAGE_NAME= 47 | GCR_SERVICE_KEY_PATH= 48 | DOCKER_ENV_FILE= 49 | DOCKER_MOUNT_ARG= 50 | 51 | ####################################### 52 | # Process command line arguments. 53 | ####################################### 54 | process_args() { 55 | # Parse options. 56 | while getopts "hc:k:e:m:" opt; do 57 | case "${opt}" in 58 | c) CONTAINER_IMAGE_NAME="${OPTARG}" ;; 59 | k) GCR_SERVICE_KEY_PATH="${OPTARG}" ;; 60 | e) DOCKER_ENV_FILE="${OPTARG}" ;; 61 | m) DOCKER_MOUNT_ARG="${OPTARG}" ;; 62 | *) usage ;; 63 | esac 64 | done 65 | shift $((OPTIND - 1)) 66 | readonly CONTAINER_IMAGE_NAME 67 | readonly GCR_SERVICE_KEY_PATH 68 | readonly DOCKER_ENV_FILE 69 | readonly DOCKER_MOUNT_ARG 70 | readonly COMMAND=("$@") 71 | } 72 | 73 | main() { 74 | process_args "$@" 75 | 76 | if [[ -z "${CONTAINER_IMAGE_NAME:-}" ]]; then 77 | echo "Running command on the host" 78 | time "${COMMAND[@]}" 79 | else 80 | echo "Running command on a new container from image ${CONTAINER_IMAGE_NAME}" 81 | if [[ ! -z "${GCR_SERVICE_KEY_PATH:-}" ]]; then 82 | # Activate service account to read from a private artifact registry repo. 83 | gcloud auth activate-service-account --key-file="${GCR_SERVICE_KEY_PATH}" 84 | gcloud config set project tink-test-infrastructure 85 | gcloud auth configure-docker us-docker.pkg.dev --quiet 86 | fi 87 | local -r path_to_mount="$(dirname "$(pwd)")" 88 | local -r library_to_test="$(basename "$(pwd)")" 89 | time docker pull "${CONTAINER_IMAGE_NAME}" 90 | 91 | local docker_opts=( 92 | --network="host" 93 | --mount type=bind,src="${path_to_mount}",dst=/deps 94 | --workdir=/deps/"${library_to_test}" 95 | --rm 96 | ) 97 | if [[ -n "${DOCKER_ENV_FILE}" ]]; then 98 | docker_opts+=( --env-file="${DOCKER_ENV_FILE}" ) 99 | fi 100 | if [[ -n ${DOCKER_MOUNT_ARG} ]] ; then 101 | docker_opts+=( --mount "${DOCKER_MOUNT_ARG}" ) 102 | fi 103 | readonly docker_opts 104 | time docker run "${docker_opts[@]}" "${CONTAINER_IMAGE_NAME}" \ 105 | bash -c "$(echo "${COMMAND[@]}")" 106 | fi 107 | } 108 | 109 | main "$@" 110 | -------------------------------------------------------------------------------- /kokoro/testutils/github_release_util.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # This script performs a source release on GitHub for a given repo, that is: 18 | # - Creates a release branch (if it does not yet exist), 19 | # - Creates a release tag. 20 | set -eo pipefail 21 | 22 | # Parameters and arguments. These will be populated/modified by args parsing. 23 | 24 | # Options. 25 | # Whether to actually create a release. This is false by default and meant to 26 | # prevent accidental releases. 27 | DO_RUN_ACTION="false" 28 | # Commit at which to make the release. If unspecified, the release is made from 29 | # HEAD. 30 | COMMIT_HASH= 31 | # Optional personal access token. 32 | ACCESS_TOKEN= 33 | 34 | # Arguments. 35 | # Action to be performed. 36 | ACTION= 37 | # This must be of the form `MAJOR.MINOR.PATCH`. 38 | VERSION= 39 | # Repo name after github.com/tink-crypto/, e.g., tink-cc. 40 | REPO_NAME= 41 | 42 | # Derived variables. 43 | GITHUB_REPO_URL= 44 | RELEASE_BRANCH= 45 | TAG= 46 | GITHUB_REFS= 47 | BRANCH_EXISTS="false" 48 | 49 | # Constants. 50 | readonly GITHUB_ORG_URL="github.com/tink-crypto" 51 | 52 | usage() { 53 | echo "Usage: $0 [-rh] [-c ] [-t ] \\" 54 | echo " " 55 | echo " : The action to be performed (crete_branch|create_tag)." 56 | echo " : The version identifier in MAJOR.MINOR.PATCH format." 57 | echo " : The name of the repository (e.g. \"tink-cc\")." 58 | echo " -c: Commit hash to use as HEAD of the release branch (optional)." 59 | echo " -t: Access token. Without this, the default is SSH (optional)." 60 | echo " -r: Whether to actually create a release; this is false by default." 61 | echo " -h: Show this help message." 62 | exit 1 63 | } 64 | 65 | process_params() { 66 | while getopts "rhc:t:" opt; do 67 | case "${opt}" in 68 | r) DO_RUN_ACTION="true" ;; 69 | c) COMMIT_HASH="${OPTARG}" ;; 70 | t) ACCESS_TOKEN="${OPTARG}" ;; 71 | *) usage ;; 72 | esac 73 | done 74 | shift $((OPTIND - 1)) 75 | readonly DO_RUN_ACTION 76 | readonly COMMIT_HASH 77 | readonly ACCESS_TOKEN 78 | 79 | ACTION="$1" 80 | if [[ ! "${ACTION}" =~ create_branch|create_tag ]]; then 81 | echo "ERROR: Expected (create_branch|create_tag) got ${ACTION}" >&2 82 | usage 83 | fi 84 | readonly ACTION 85 | 86 | VERSION="$2" 87 | if [[ ! "${VERSION}" =~ ^[0-9]+.[0-9]+.[0-9]+$ ]]; then 88 | echo "ERROR: Invalid version format: expected MAJOR.MINOR.PATCH, got \ 89 | ${VERSION}" >&2 90 | usage 91 | fi 92 | readonly VERSION 93 | 94 | REPO_NAME="$3" 95 | if [[ -z "${REPO_NAME}" ]]; then 96 | echo "ERROR: Repo name must be specified." >&2 97 | usage 98 | fi 99 | readonly REPO_NAME 100 | 101 | # Use SSH by default. 102 | local protocol_and_credentials="ssh://git" 103 | if [[ -n "${ACCESS_TOKEN}" ]]; then 104 | protocol_and_credentials="https://ise-crypto:${ACCESS_TOKEN}" 105 | fi 106 | readonly protocol_and_credentials 107 | GITHUB_REPO_URL="${protocol_and_credentials}@${GITHUB_ORG_URL}/${REPO_NAME}" 108 | readonly GITHUB_REPO_URL 109 | 110 | # Release branch is only MAJOR.MINOR. 111 | readonly RELEASE_BRANCH="$(echo "${VERSION}" | cut -d'.' -f1,2)" 112 | 113 | # Splitting declaration and assignment guarantees correct propagation of the 114 | # exit code of the subshell. 115 | local GITHUB_REFS 116 | GITHUB_REFS="$(git ls-remote "${GITHUB_REPO_URL}")" 117 | readonly GITHUB_REFS 118 | 119 | local -r expected_release_branch="refs/heads/${RELEASE_BRANCH}" 120 | if echo "${GITHUB_REFS}" | grep "${expected_release_branch}" > /dev/null; then 121 | BRANCH_EXISTS="true" 122 | fi 123 | readonly BRANCH_EXISTS 124 | 125 | if [[ "${ACTION}" == "create_tag" ]]; then 126 | if [[ "${BRANCH_EXISTS}" == "false" ]]; then 127 | echo "ERROR: The release branch does not exist in \ 128 | ${GITHUB_ORG_URL}/${REPO_NAME}." >&2 129 | return 1 130 | fi 131 | local -r release_tag="v${VERSION}" 132 | local -r expected_release_tag="refs/tags/${release_tag}" 133 | if echo "${GITHUB_REFS}" | grep "${expected_release_tag}" > /dev/null; then 134 | echo "ERROR The tag \"${release_tag}\" already exists in \ 135 | ${GITHUB_ORG_URL}/${REPO_NAME}." >&2 136 | return 1 137 | fi 138 | 139 | fi 140 | } 141 | 142 | ####################################### 143 | # Prints a command 144 | # 145 | # Args: 146 | # Command to execute. 147 | # 148 | ####################################### 149 | print_command() { 150 | printf '%q ' '+' "$@" 151 | echo 152 | } 153 | 154 | ####################################### 155 | # Runs a command if DO_RUN_ACTION is true. 156 | # 157 | # Args: 158 | # Command to execute. 159 | # Globals: 160 | # DO_RUN_ACTION 161 | # 162 | ####################################### 163 | run_command() { 164 | if [[ "${DO_RUN_ACTION}" == "false" ]]; then 165 | echo " *** Dry run, command not executed. ***" 166 | return 0 167 | fi 168 | # Actually run the command. 169 | "$@" 170 | return $? 171 | } 172 | 173 | ####################################### 174 | # Prints and runs a command. 175 | # 176 | # Args: 177 | # Command to execute. 178 | # 179 | ####################################### 180 | print_and_run_command() { 181 | print_command "$@" 182 | run_command "$@" 183 | } 184 | 185 | ####################################### 186 | # Creates and checks out to the release branch. 187 | # 188 | # If COMMIT_HASH is specified, use COMMIT_HASH as HEAD for the branch. 189 | # 190 | # Globals: 191 | # RELEASE_BRANCH 192 | # COMMIT_HASH 193 | # 194 | ####################################### 195 | git_create_release_branch() { 196 | if [[ "${BRANCH_EXISTS}" == "true" ]]; then 197 | echo "WARNING: The release branch already exists. Nothing to do." 198 | return 0 199 | fi 200 | # Target branch does not exist so we create the release branch. 201 | if [[ -n "${COMMIT_HASH:-}" ]]; then 202 | # Use COMMIT_HASH as HEAD for this branch. 203 | print_and_run_command git branch "${RELEASE_BRANCH}" "${COMMIT_HASH}" 204 | else 205 | print_and_run_command git branch "${RELEASE_BRANCH}" 206 | fi 207 | print_and_run_command git push origin "${RELEASE_BRANCH}" 208 | } 209 | 210 | ####################################### 211 | # Creates a release tag. 212 | # 213 | # Globals: 214 | # RELEASE_BRANCH 215 | # REPO_NAME 216 | # VERSION 217 | # 218 | ####################################### 219 | git_create_release_tag() { 220 | if [[ "${BRANCH_EXISTS}" == "false" ]]; then 221 | echo "ERROR: The release branch does not exist in \ 222 | ${GITHUB_ORG_URL}/${REPO_NAME}." >&2 223 | return 1 224 | fi 225 | local -r release_tag="v${VERSION}" 226 | local -r expected_release_tag="refs/tags/${release_tag}" 227 | if echo "${GITHUB_REFS}" | grep "${expected_release_tag}" > /dev/null; then 228 | echo "ERROR The tag \"${release_tag}\" already exists in \ 229 | ${GITHUB_ORG_URL}/${REPO_NAME}." >&2 230 | return 1 231 | fi 232 | print_and_run_command git checkout "${RELEASE_BRANCH}" 233 | print_and_run_command git tag -a "${release_tag}" \ 234 | -m "${REPO_NAME} version ${VERSION}" 235 | print_and_run_command git push origin "${release_tag}" 236 | } 237 | 238 | main() { 239 | process_params "$@" 240 | # Avoid logging the full URL; replace GIT_URL with a version that omits user 241 | # and access token. 242 | local -r protocol="$(echo "${GITHUB_REPO_URL}" | cut -d':' -f1)" 243 | local -r github_repo="$(echo "${GITHUB_REPO_URL}" | cut -d'@' -f2)" 244 | print_command git clone "${protocol}://...@${github_repo}" 245 | run_command git clone "${GITHUB_REPO_URL}" 246 | print_and_run_command cd "${REPO_NAME}" 247 | 248 | case "${ACTION}" in 249 | create_branch) git_create_release_branch ;; 250 | create_tag) git_create_release_tag ;; 251 | esac 252 | } 253 | 254 | main "$@" 255 | -------------------------------------------------------------------------------- /kokoro/testutils/java_test_container_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2023 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | _image_prefix() { 18 | local -r artifact_registry_url="us-docker.pkg.dev" 19 | local -r test_project="tink-test-infrastructure" 20 | local -r artifact_registry_repo="tink-ci-images" 21 | echo "${artifact_registry_url}/${test_project}/${artifact_registry_repo}" 22 | } 23 | 24 | # Linux container images for Tink Java libraries. 25 | readonly TINK_JAVA_BASE_IMAGE_NAME="linux-tink-java-base" 26 | readonly TINK_JAVA_BASE_IMAGE_HASH="8c7e62f2244930bc2c22a5a20d6d8c4df39abff94af06142cce8137d173fc744" 27 | readonly TINK_JAVA_BASE_IMAGE="$(_image_prefix)/${TINK_JAVA_BASE_IMAGE_NAME}@sha256:${TINK_JAVA_BASE_IMAGE_HASH}" 28 | 29 | readonly TINK_JAVA_GCLOUD_IMAGE_NAME="linux-tink-java-gcloud" 30 | readonly TINK_JAVA_GCLOUD_IMAGE_HASH="c8d60059837693aa17e8cada4fea00da69563a1006375c42a7d0e0511fb1fd82" 31 | readonly TINK_JAVA_GCLOUD_IMAGE="$(_image_prefix)/${TINK_JAVA_GCLOUD_IMAGE_NAME}@sha256:${TINK_JAVA_GCLOUD_IMAGE_HASH}" 32 | 33 | unset -f _image_prefix 34 | -------------------------------------------------------------------------------- /kokoro/testutils/run_bazel_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # This script runs all the Bazel tests within a given workspace directory. 18 | # 19 | # Users must spcify the WORKSPACE directory. Optionally, the user can specify 20 | # a set of additional manual targets to run. 21 | # 22 | 23 | # Note: -E extends the trap to shell functions, command substitutions, and 24 | # commands executed in a subshell environment. 25 | set -eEo pipefail 26 | # Print some debug output on error before exiting. 27 | trap print_debug_output ERR 28 | 29 | usage() { 30 | echo "Usage: $0 [-mh] [-c bazel_cache_name] [-b ...] \\" 31 | echo " [-t ...] \\" 32 | echo " [ ...]" 33 | echo " -m: Runs only the manual targets. If set, manual targets must be" 34 | echo " provided." 35 | echo " -b: Comma separated list of flags to pass to `bazel build`." 36 | echo " -t: Comma separated list of flags to pass to `bazel test`." 37 | echo " -h: Help. Print this usage information." 38 | echo " -c: Bazel cache to use; creadentials are expected to be in a" 39 | echo " cache_key file." 40 | exit 1 41 | } 42 | 43 | readonly PLATFORM="$(uname | tr '[:upper:]' '[:lower:]')" 44 | MANUAL_ONLY="false" 45 | WORKSPACE_DIR= 46 | MANUAL_TARGETS= 47 | BAZEL_CMD="bazel" 48 | BUILD_FLAGS=() 49 | TEST_FLAGS=() 50 | CACHE_FLAGS=() 51 | 52 | ####################################### 53 | # Process command line arguments. 54 | # 55 | # Globals: 56 | # WORKSPACE_DIR 57 | # MANUAL_TARGETS 58 | ####################################### 59 | process_args() { 60 | # Parse options. 61 | while getopts "mb:t:c:" opt; do 62 | case "${opt}" in 63 | m) MANUAL_ONLY="true" ;; 64 | b) BUILD_FLAGS=($(echo "${OPTARG}" | tr ',' '\n')) ;; 65 | t) TEST_FLAGS=($(echo "${OPTARG}" | tr ',' '\n')) ;; 66 | c) CACHE_FLAGS=( 67 | "--remote_cache=https://storage.googleapis.com/${OPTARG}" 68 | "--google_credentials=$(realpath ./cache_key)" 69 | );; 70 | *) usage ;; 71 | esac 72 | done 73 | shift $((OPTIND - 1)) 74 | 75 | WORKSPACE_DIR="$1" 76 | readonly WORKSPACE_DIR 77 | 78 | if [[ -z "${WORKSPACE_DIR}" ]]; then 79 | usage 80 | fi 81 | 82 | shift 1 83 | MANUAL_TARGETS=("$@") 84 | readonly MANUAL_TARGETS 85 | 86 | if [[ "${MANUAL_ONLY}" == "true" ]] && (( ${#MANUAL_TARGETS[@]} == 0 )); then 87 | usage 88 | fi 89 | 90 | # Use Bazelisk (https://github.com/bazelbuild/bazelisk) if available. 91 | if command -v "bazelisk" &> /dev/null; then 92 | BAZEL_CMD="bazelisk" 93 | fi 94 | readonly BAZEL_CMD 95 | echo "Using: $(which ${BAZEL_CMD})" 96 | 97 | readonly CACHE_FLAGS 98 | } 99 | 100 | ####################################### 101 | # Print some debugging output. 102 | ####################################### 103 | print_debug_output() { 104 | ls -l 105 | df -h 106 | } 107 | 108 | main() { 109 | process_args "$@" 110 | 111 | TEST_FLAGS+=( 112 | --strategy=TestRunner=standalone 113 | --test_output=all 114 | ) 115 | 116 | local -r workspace_dir="$(cd ${WORKSPACE_DIR} && pwd)" 117 | 118 | if [[ "${PLATFORM}" == 'darwin' ]]; then 119 | TEST_FLAGS+=( --jvmopt="-Djava.net.preferIPv6Addresses=true" ) 120 | if [[ "${workspace_dir}" =~ javascript ]]; then 121 | BUILD_FLAGS+=( --experimental_inprocess_symlink_creation ) 122 | TEST_FLAGS+=( --experimental_inprocess_symlink_creation ) 123 | fi 124 | fi 125 | readonly BUILD_FLAGS 126 | readonly TEST_FLAGS 127 | ( 128 | set -x 129 | cd "${workspace_dir}" 130 | if [[ "${MANUAL_ONLY}" == "false" ]]; then 131 | time "${BAZEL_CMD}" build "${CACHE_FLAGS[@]}" "${BUILD_FLAGS[@]}" -- ... 132 | # Exit code 4 means targets build correctly but no tests were found. See 133 | # https://bazel.build/docs/scripts#exit-codes. 134 | bazel_test_return=0 135 | time "${BAZEL_CMD}" test "${CACHE_FLAGS[@]}" "${TEST_FLAGS[@]}" -- ... \ 136 | || bazel_test_return="$?" 137 | if (( $bazel_test_return != 0 && $bazel_test_return != 4 )); then 138 | return "${bazel_test_return}" 139 | fi 140 | fi 141 | # Run specific manual targets. 142 | if (( ${#MANUAL_TARGETS[@]} > 0 )); then 143 | time "${BAZEL_CMD}" build "${CACHE_FLAGS[@]}" "${BUILD_FLAGS[@]}" -- \ 144 | "${MANUAL_TARGETS[@]}" 145 | time "${BAZEL_CMD}" test "${CACHE_FLAGS[@]}" "${TEST_FLAGS[@]}" -- \ 146 | "${MANUAL_TARGETS[@]}" 147 | fi 148 | ) 149 | } 150 | 151 | main "$@" 152 | -------------------------------------------------------------------------------- /kokoro/testutils/test_utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | # Set of utilities to run unit tests for bash scripts. 18 | # 19 | # Example usage: 20 | # From your test script: 21 | # source some/path/to/test_utils.sh 22 | # 23 | # # Test functions must be defined as follows: 24 | # test__() { 25 | # # Do some ground work. 26 | # # Run the test script. 27 | # ./path/to/script_to_test ... 28 | # ASSERT_CMD_SUCCEEDED 29 | # ASSERT_FILE_EQUALS 30 | # } 31 | # 32 | # # Finds all the functions starting with `test_`, extracts test name and 33 | # # test case, and run them. 34 | # run_all_tests "$@" 35 | # 36 | 37 | # This is either set by Bazel or generated. 38 | : "${TEST_TMPDIR:="$(mktemp -td test.XXXXX)"}" 39 | readonly TEST_TMPDIR 40 | 41 | # Temporary directory for the testcase to use. 42 | TEST_CASE_TMPDIR= 43 | 44 | # Current test name. 45 | _CURRENT_TEST_SCOPE= 46 | 47 | # Current test case. 48 | _CURRENT_TEST_CASE= 49 | 50 | # True if at least one of the test cases terminated with an error. 51 | _HAS_ERROR="false" 52 | 53 | _print_testcase_failed_and_exit() { 54 | echo "[ FAILED ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" 55 | exit 1 56 | } 57 | 58 | ####################################### 59 | # Starts a new test case. 60 | # 61 | # Globals: 62 | # _CURRENT_TEST_SCOPE 63 | # _CURRENT_TEST_CASE 64 | ####################################### 65 | _start_test_case() { 66 | echo "[ RUN ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" 67 | # Create a tmp dir for the test case. 68 | TEST_CASE_TMPDIR="${TEST_TMPDIR}/${_CURRENT_TEST_SCOPE}/${_CURRENT_TEST_CASE}" 69 | mkdir -p "${TEST_CASE_TMPDIR}" 70 | } 71 | 72 | ####################################### 73 | # Ends a test case printing a success message. 74 | # 75 | # Globals: 76 | # _CURRENT_TEST_SCOPE 77 | # _CURRENT_TEST_CASE 78 | ####################################### 79 | _end_test_case_with_success() { 80 | test_case="$1" 81 | echo "[ OK ] ${_CURRENT_TEST_SCOPE}.${_CURRENT_TEST_CASE}" 82 | } 83 | 84 | ####################################### 85 | # Returns the list of tests defined in the test script. 86 | # 87 | # A test case is a function of the form: 88 | # test__ 89 | # 90 | # This function returns all the functions starting with `test_`. 91 | # 92 | # Globals: 93 | # None 94 | # Arguments: 95 | # None 96 | ####################################### 97 | _get_all_tests() { 98 | declare -F | 99 | while read line; do 100 | case "${line}" in "declare -f test_"*) 101 | echo "${line#declare -f }" 102 | ;; 103 | esac 104 | done 105 | } 106 | 107 | ####################################### 108 | # Runs a given test function. 109 | # 110 | # A test case is a function of the form: 111 | # test__ 112 | # 113 | # This script extracts test name and test case from the name. 114 | # 115 | # Globals: 116 | # _CURRENT_TEST_SCOPE 117 | # Arguments: 118 | # None 119 | ####################################### 120 | _do_run_test() { 121 | test_function="$1" 122 | IFS=_ read _CURRENT_TEST_SCOPE _CURRENT_TEST_CASE <<< "${test_function#test_}" 123 | _start_test_case 124 | ( 125 | # Make sure we exit only when assertions fail. 126 | set +e 127 | "${test_function}" 128 | ) 129 | local -r result=$? 130 | if (( $result == 0 )); then 131 | _end_test_case_with_success 132 | else 133 | _HAS_ERROR="true" 134 | fi 135 | } 136 | 137 | ####################################### 138 | # Runs all the test cases defined in the test script file. 139 | # Globals: 140 | # None 141 | # Arguments: 142 | # None 143 | # 144 | ####################################### 145 | run_all_tests() { 146 | for test in $(_get_all_tests); do 147 | _do_run_test "${test}" 148 | done 149 | # Make sure we return an error code for the failing test 150 | if [[ "${_HAS_ERROR}" == "true" ]]; then 151 | exit 1 152 | fi 153 | } 154 | 155 | ASSERT_CMD_SUCCEEDED() { 156 | if (( $? != 0 )); then 157 | _print_testcase_failed_and_exit 158 | fi 159 | } 160 | 161 | ASSERT_CMD_FAILED() { 162 | if (( $? == 0 )); then 163 | _print_testcase_failed_and_exit 164 | fi 165 | } 166 | 167 | ASSERT_FILE_EQUALS() { 168 | input_file="$1" 169 | expected_file="$2" 170 | if ! diff "${input_file}" "${expected_file}"; then 171 | _print_testcase_failed_and_exit 172 | fi 173 | } 174 | -------------------------------------------------------------------------------- /kokoro/testutils/update_android_sdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | ################################################################################ 17 | 18 | # Installs the Android SDK and tools and sets the ANDROID_HOME environment 19 | # variable if running on Kokoro. 20 | set -x 21 | 22 | if [[ -z "${KOKORO_ROOT}" ]] ; then 23 | exit 0 24 | fi 25 | 26 | readonly ANDROID_COMMANDLINETOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip" 27 | readonly ANDROID_COMMANDLINETOOLS_SHA256="2ccbda4302db862a28ada25aa7425d99dce9462046003c1714b059b5c47970d8" 28 | readonly PLATFORM="$(uname | tr '[:upper:]' '[:lower:]')" 29 | 30 | export ANDROID_HOME="/tmp/android-sdk" 31 | 32 | if [[ "${PLATFORM}" == 'darwin' ]]; then 33 | export JAVA_OPTS="-Djava.net.preferIPv6Addresses=true" 34 | fi 35 | 36 | mkdir -p "${ANDROID_HOME}" 37 | time curl -LsS "${ANDROID_COMMANDLINETOOLS_URL}" -o cmdline-tools.zip 38 | echo "${ANDROID_COMMANDLINETOOLS_SHA256} cmdline-tools.zip" | sha256sum -c 39 | unzip cmdline-tools.zip -d "${ANDROID_HOME}" 40 | # Discard STDOUT due to noisy progress bar which can't be silenced. 41 | (yes || true) | "${ANDROID_HOME}/cmdline-tools/bin/sdkmanager" \ 42 | "--sdk_root=${ANDROID_HOME}" \ 43 | "build-tools;30.0.3" \ 44 | "platforms;android-23" \ 45 | "platforms;android-26" \ 46 | > /dev/null 47 | rm -rf cmdline-tools.zip 48 | -------------------------------------------------------------------------------- /maven/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | licenses(["notice"]) 4 | 5 | filegroup( 6 | name = "maven_settings", 7 | srcs = ["settings.xml"], 8 | ) 9 | 10 | sh_binary( 11 | name = "maven_deploy_library", 12 | srcs = ["maven_deploy_library.sh"], 13 | target_compatible_with = [ 14 | "@platforms//os:linux", 15 | ], 16 | ) 17 | 18 | sh_test( 19 | name = "maven_deploy_library_test", 20 | size = "small", 21 | srcs = ["maven_deploy_library_test.sh"], 22 | target_compatible_with = [ 23 | "@platforms//os:linux", 24 | ], 25 | args = [ 26 | "$(rlocationpath :maven_deploy_library)", 27 | "$(rlocationpath //kokoro/testutils:test_utils)", 28 | "$(rlocationpath :maven_settings)", 29 | ], 30 | data = [ 31 | ":maven_deploy_library", 32 | ":maven_settings", 33 | "//kokoro/testutils:test_utils", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /maven/maven_deploy_library.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | ################################################################################ 17 | 18 | # Deploys a Maven library. 19 | # 20 | # NOTE: 21 | # - This must be run from the root of the library workspace. 22 | 23 | usage() { 24 | cat < 27 | " 28 | -d: Dry run. Only execute idempotent commands (default: false). 29 | -n: JARs name prefix. Prefix to apply to JAR names (default: ). 30 | -u: GitHub URL. GitHub URL for Javadoc publishing; it is mandatory when 31 | is "snaphot" or "release". 32 | -c: Bazel cache to use; credentials are expected to be in the file 33 | ./cache_key. 34 | -h: Help. Print this usage information. 35 | EOF 36 | exit 1 37 | } 38 | 39 | # Arguments to use for all git invocations. 40 | readonly GIT_ARGS=( 41 | -c user.email=noreply@google.com 42 | -c user.name="Tink Team" 43 | ) 44 | 45 | to_absolute_path() { 46 | local -r path="$1" 47 | echo "$(cd "$(dirname "${path}")" && pwd)/$(basename "${path}")" 48 | } 49 | 50 | # Options. 51 | DRY_RUN="false" 52 | GIT_URL= 53 | JAR_NAME_PREFIX= 54 | 55 | # Positional arguments. 56 | LIBRARY_NAME= 57 | POM_FILE= 58 | ARTIFACT_VERSION= 59 | 60 | # Other. 61 | BAZEL_CMD="bazel" 62 | MAVEN_ARGS=() 63 | CACHE_FLAGS=() 64 | 65 | parse_args() { 66 | # Parse options. 67 | while getopts "dhn::u::c:" opt; do 68 | case "${opt}" in 69 | d) DRY_RUN="true" ;; 70 | n) JAR_NAME_PREFIX="${OPTARG}" ;; 71 | u) GIT_URL="${OPTARG}" ;; 72 | c) CACHE_FLAGS=( 73 | "--remote_cache=https://storage.googleapis.com/${OPTARG}" 74 | "--google_credentials=$(to_absolute_path ./cache_key)" 75 | ) ;; 76 | *) usage ;; 77 | esac 78 | done 79 | shift $((OPTIND - 1)) 80 | 81 | readonly DRY_RUN 82 | readonly JAR_NAME_PREFIX 83 | readonly GIT_URL 84 | readonly CACHE_FLAGS 85 | 86 | # Parse args. 87 | if (( $# < 4 )); then 88 | usage 89 | fi 90 | ACTION="$1" 91 | LIBRARY_NAME="$2" 92 | POM_FILE="$3" 93 | ARTIFACT_VERSION="$4" 94 | 95 | # Make sure the version has the correct format. 96 | if [[ ! "${ARTIFACT_VERSION}" =~ (^HEAD$|^[0-9]+\.[0-9]+\.[0-9]$) ]]; then 97 | usage 98 | fi 99 | 100 | if [[ ! -f "${POM_FILE}" ]]; then 101 | echo "ERROR: The POM file doesn't exist: ${POM_FILE}" >&2 102 | usage 103 | fi 104 | 105 | local -r maven_scripts_dir="$(cd "$(dirname "${POM_FILE}")" && pwd)" 106 | case "${ACTION}" in 107 | install) 108 | MAVEN_ARGS+=( "install:install-file" ) 109 | ARTIFACT_VERSION="${ARTIFACT_VERSION}-SNAPSHOT" 110 | ;; 111 | snapshot) 112 | if [[ -z "${GIT_URL}" ]]; then 113 | usage 114 | fi 115 | MAVEN_ARGS+=( 116 | "deploy:deploy-file" 117 | "-DrepositoryId=ossrh" 118 | "-Durl=https://oss.sonatype.org/content/repositories/snapshots" 119 | "--settings=${maven_scripts_dir}/settings.xml" 120 | ) 121 | ARTIFACT_VERSION="${ARTIFACT_VERSION}-SNAPSHOT" 122 | ;; 123 | release) 124 | if [[ -z "${GIT_URL}" ]]; then 125 | usage 126 | fi 127 | MAVEN_ARGS+=( 128 | "gpg:sign-and-deploy-file" 129 | "-DrepositoryId=ossrh" 130 | "-Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/" 131 | "-Dgpg.keyname=tink-dev@google.com" 132 | "--settings=${maven_scripts_dir}/settings.xml" 133 | ) 134 | ;; 135 | *) 136 | usage 137 | ;; 138 | esac 139 | 140 | if command -v "bazelisk" &> /dev/null; then 141 | BAZEL_CMD="bazelisk" 142 | fi 143 | readonly BAZEL_CMD 144 | 145 | readonly ACTION 146 | readonly LIBRARY_NAME 147 | readonly POM_FILE 148 | readonly ARTIFACT_VERSION 149 | readonly MAVEN_ARGS 150 | } 151 | 152 | do_run_command() { 153 | if ! "$@"; then 154 | echo "*** Failed executing command. ***" 155 | echo "Failed command: $@" 156 | exit 1 157 | fi 158 | return $? 159 | } 160 | 161 | print_command() { 162 | printf '%q ' '+' "$@" 163 | echo 164 | } 165 | 166 | print_and_do() { 167 | print_command "$@" 168 | do_run_command "$@" 169 | return $? 170 | } 171 | 172 | ####################################### 173 | # Runs a given command if DRY_RUN isn't true. 174 | # Globals: 175 | # DRY_RUN 176 | # Arguments: 177 | # The command to run and its arguments. 178 | ####################################### 179 | do_run_if_not_dry_run() { 180 | print_command "$@" 181 | if [[ "${DRY_RUN}" == "true" ]]; then 182 | echo " *** Dry run, command not executed. ***" 183 | return 0 184 | fi 185 | do_run_command "$@" 186 | return $? 187 | } 188 | 189 | echo_output_file() { 190 | local workspace_dir="$1" 191 | local library="$2" 192 | 193 | ( 194 | cd "${workspace_dir}" 195 | local file="bazel-bin/${library}" 196 | if [[ ! -e "${file}" ]]; then 197 | file="bazel-genfiles/${library}" 198 | fi 199 | if [[ ! -e "${file}" ]]; then 200 | echo "Could not find Bazel output file for ${library}" 201 | exit 1 202 | fi 203 | echo -n "${workspace_dir}/${file}" 204 | ) 205 | } 206 | 207 | ####################################### 208 | # Pusblishes Javadoc to GitHub pages. 209 | # 210 | # Globals: 211 | # ACTION 212 | # GIT_ARGS 213 | # GIT_URL 214 | # LIBRARY_NAME 215 | # ARTIFACT_VERSION 216 | # Arguments: 217 | # workspace_dir: Workspace directory for the library. 218 | # javadoc: Javadoc library name. 219 | ####################################### 220 | publish_javadoc_to_github_pages() { 221 | if [[ "${ACTION}" == "install" ]]; then 222 | echo "Local deployment, skipping publishing javadoc to GitHub Pages..." 223 | return 0 224 | fi 225 | 226 | local workspace_dir="$1" 227 | local javadoc="$2" 228 | 229 | local -r javadoc_file="$(echo_output_file "${workspace_dir}" "${javadoc}")" 230 | 231 | print_and_do rm -rf gh-pages 232 | print_and_do git "${GIT_ARGS[@]}" clone \ 233 | --quiet --branch=gh-pages "${GIT_URL}" gh-pages > /dev/null 234 | ( 235 | print_and_do cd gh-pages 236 | if [ -d "javadoc/${LIBRARY_NAME}/${ARTIFACT_VERSION}" ]; then 237 | print_and_do git "${GIT_ARGS[@]}" rm -rf \ 238 | "javadoc/${LIBRARY_NAME}/${ARTIFACT_VERSION}" 239 | fi 240 | print_and_do mkdir -p "javadoc/${LIBRARY_NAME}/${ARTIFACT_VERSION}" 241 | print_and_do unzip "${javadoc_file}" \ 242 | -d "javadoc/${LIBRARY_NAME}/${ARTIFACT_VERSION}" 243 | print_and_do rm -rf "javadoc/${LIBRARY_NAME}/${ARTIFACT_VERSION}/META-INF/" 244 | print_and_do git "${GIT_ARGS[@]}" add \ 245 | -f "javadoc/${LIBRARY_NAME}/${ARTIFACT_VERSION}" 246 | if [[ "$(git "${GIT_ARGS[@]}" status --porcelain)" ]]; then 247 | # Changes exist. 248 | do_run_if_not_dry_run \ 249 | git "${GIT_ARGS[@]}" commit \ 250 | -m "${LIBRARY_NAME}-${ARTIFACT_VERSION} Javadoc auto-pushed to gh-pages" 251 | 252 | do_run_if_not_dry_run \ 253 | git "${GIT_ARGS[@]}" push -fq origin gh-pages > /dev/null 254 | echo -e "Published Javadoc to gh-pages.\n" 255 | else 256 | # No changes exist. 257 | echo -e "No changes in ${LIBRARY_NAME}-${ARTIFACT_VERSION} Javadoc.\n" 258 | fi 259 | ) 260 | } 261 | 262 | main() { 263 | parse_args "$@" 264 | 265 | local -r jars_name_prefix="${JAR_NAME_PREFIX:-${LIBRARY_NAME}}" 266 | local -r library="${jars_name_prefix}.jar" 267 | local -r src_jar="${jars_name_prefix}-src.jar" 268 | local -r javadoc="${jars_name_prefix}-javadoc.jar" 269 | 270 | local -r workspace_dir="$(pwd)" 271 | 272 | print_and_do "${BAZEL_CMD}" build "${CACHE_FLAGS[@]}" "${library}" \ 273 | "${src_jar}" "${javadoc}" 274 | 275 | local -r library_file="$(echo_output_file "${workspace_dir}" "${library}")" 276 | local -r src_jar_file="$(echo_output_file "${workspace_dir}" "${src_jar}")" 277 | local -r javadoc_file="$(echo_output_file "${workspace_dir}" "${javadoc}")" 278 | 279 | # Update the version in the POM file. 280 | do_run_if_not_dry_run sed -i \ 281 | 's/VERSION_PLACEHOLDER/'"${ARTIFACT_VERSION}"'/' "${POM_FILE}" 282 | 283 | do_run_if_not_dry_run mvn "${MAVEN_ARGS[@]}" -Dfile="${library_file}" \ 284 | -Dsources="${src_jar_file}" -Djavadoc="${javadoc_file}" \ 285 | -DpomFile="${POM_FILE}" 286 | 287 | # Add the placeholder back in the POM file. 288 | do_run_if_not_dry_run sed -i \ 289 | 's/'"${ARTIFACT_VERSION}"'/VERSION_PLACEHOLDER/' "${POM_FILE}" 290 | 291 | publish_javadoc_to_github_pages "${workspace_dir}" "${javadoc}" 292 | } 293 | 294 | main "$@" 295 | -------------------------------------------------------------------------------- /maven/maven_deploy_library_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2023 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ################################################################################ 16 | 17 | DEFAULT_DIR="$(pwd)" 18 | if [[ -n "${TEST_SRCDIR}" ]]; then 19 | DEFAULT_DIR="${TEST_SRCDIR}" 20 | fi 21 | readonly DEFAULT_DIR 22 | 23 | readonly CLI_RELATIVE_PATH="${1:-"maven_deploy_library.sh"}" 24 | readonly CLI="${DEFAULT_DIR}/${CLI_RELATIVE_PATH}" 25 | readonly TEST_UTILS="${DEFAULT_DIR}/${2}" 26 | readonly SETTINGS_XML="${DEFAULT_DIR}/${3}" 27 | 28 | readonly RELATIVE_MVN_DIR_PATH="$(dirname "${CLI_RELATIVE_PATH}")" 29 | 30 | readonly ACTUAL_MVN_CMD="$(which mvn)" 31 | 32 | # Load the test library. 33 | source "${TEST_UTILS}" 34 | 35 | create_maven_folder_and_pom_file() { 36 | mkdir -p "${RELATIVE_MVN_DIR_PATH}" 37 | cp "${SETTINGS_XML}" "${RELATIVE_MVN_DIR_PATH}" 38 | cat << EOF > "${RELATIVE_MVN_DIR_PATH}/tink.pom.xml" 39 | 42 | 4.0.0 43 | Tink Cryptography API 44 | Tink is a small cryptographic library that provides a safe, simple, agile and fast way to accomplish some common cryptographic tasks. 45 | 46 | 47 | org.sonatype.oss 48 | oss-parent 49 | 7 50 | 51 | 52 | com.google.crypto.tink 53 | tink 54 | VERSION_PLACEHOLDER 55 | 56 | 57 | 58 | EOF 59 | } 60 | 61 | # Mock Bazel command that pretends to build a given target. 62 | mock_bazel() { 63 | local -r bazel_cmd="$1" 64 | case "${bazel_cmd}" in 65 | "build") 66 | mkdir bazel-bin/ 67 | touch bazel-bin/tink.jar 68 | touch bazel-bin/tink-src.jar 69 | # Create the javadoc zip. 70 | mkdir META-INF 71 | touch META-INF/MANIFEST.MF 72 | zip bazel-bin/tink-javadoc.jar META-INF/ 73 | ;; 74 | *) return 1 ;; # No other command should be called. 75 | esac 76 | } 77 | 78 | test_MavenDeployLibraryTest_InstallSucceeds() { 79 | cd "${TEST_CASE_TMPDIR}" 80 | create_maven_folder_and_pom_file 81 | 82 | cat << EOF > expected_commands.txt 83 | bazel build tink.jar tink-src.jar tink-javadoc.jar 84 | mvn install:install-file -Dfile=${TEST_CASE_TMPDIR}/bazel-bin/tink.jar \ 85 | -Dsources=${TEST_CASE_TMPDIR}/bazel-bin/tink-src.jar \ 86 | -Djavadoc=${TEST_CASE_TMPDIR}/bazel-bin/tink-javadoc.jar \ 87 | -DpomFile=${RELATIVE_MVN_DIR_PATH}/tink.pom.xml 88 | EOF 89 | 90 | bazel() { 91 | echo "bazel $@" >> actual_commands.txt 92 | mock_bazel "$@" 93 | } 94 | 95 | bazelisk() { 96 | bazel "$@" 97 | } 98 | 99 | mvn() { 100 | echo "mvn $@" >> actual_commands.txt 101 | } 102 | 103 | ( 104 | source "${CLI}" install tink "${RELATIVE_MVN_DIR_PATH}/tink.pom.xml" HEAD \ 105 | > /dev/null 106 | ) 107 | ASSERT_CMD_SUCCEEDED 108 | ASSERT_FILE_EQUALS actual_commands.txt expected_commands.txt 109 | } 110 | 111 | test_MavenDeployLibraryTest_RemoteCachingIsUsedWhenSet() { 112 | cd "${TEST_CASE_TMPDIR}" 113 | create_maven_folder_and_pom_file 114 | 115 | local -r remote_caching_name="some_cache_path" 116 | local -r cache_key_file="$(pwd)/cache_key" 117 | touch "${cache_key_file}" 118 | 119 | cat << EOF > expected_commands.txt 120 | bazel build \ 121 | --remote_cache=https://storage.googleapis.com/${remote_caching_name} \ 122 | --google_credentials=${cache_key_file} tink.jar tink-src.jar tink-javadoc.jar 123 | mvn install:install-file -Dfile=${TEST_CASE_TMPDIR}/bazel-bin/tink.jar \ 124 | -Dsources=${TEST_CASE_TMPDIR}/bazel-bin/tink-src.jar \ 125 | -Djavadoc=${TEST_CASE_TMPDIR}/bazel-bin/tink-javadoc.jar \ 126 | -DpomFile=${RELATIVE_MVN_DIR_PATH}/tink.pom.xml 127 | EOF 128 | 129 | bazel() { 130 | echo "bazel $@" >> actual_commands.txt 131 | mock_bazel "$@" 132 | } 133 | 134 | bazelisk() { 135 | bazel "$@" 136 | } 137 | 138 | mvn() { 139 | echo "mvn $@" >> actual_commands.txt 140 | } 141 | 142 | ( 143 | source "${CLI}" -c ${remote_caching_name} install tink \ 144 | "${RELATIVE_MVN_DIR_PATH}/tink.pom.xml" HEAD > /dev/null 145 | ) 146 | rm -rf "${cache_key_file}" 147 | ASSERT_CMD_SUCCEEDED 148 | ASSERT_FILE_EQUALS actual_commands.txt expected_commands.txt 149 | } 150 | 151 | test_MavenDeployLibraryTest_ReleaseFailsIfNoUrl() { 152 | cd "${TEST_CASE_TMPDIR}" 153 | create_maven_folder_and_pom_file 154 | # No calls to bazel should be made. 155 | bazel() { 156 | return 1 157 | } 158 | bazelisk() { 159 | bazel "$@" 160 | } 161 | # No calls to mvn should be made. 162 | mvn() { 163 | return 1 164 | } 165 | ( 166 | source "${CLI}" release tink "${RELATIVE_MVN_DIR_PATH}/tink.pom.xml" 1.2.3 \ 167 | > /dev/null 168 | ) 169 | ASSERT_CMD_FAILED 170 | } 171 | 172 | 173 | create_test_gpg_key() { 174 | gpg --pinentry-mode loopback --gen-key --batch << EOF 175 | Key-Type: 1 176 | Key-Length: 2048 177 | Subkey-Type: 1 178 | Subkey-Length: 2048 179 | Name-Real: Test User 180 | Name-Email: test@test.com 181 | Expire-Date: 0 182 | Passphrase: This-is-a-passphrase 183 | EOF 184 | } 185 | 186 | delete_gpg_test_key() { 187 | gpg --list-secret-keys --with-colon --fingerprint test@test.com\ 188 | | grep -m 1 fpr \ 189 | | sed -n 's/^fpr:::::::::\([[:alnum:]]\+\):/\1/p' \ 190 | | xargs gpg --delete-secret-keys --batch --yes 191 | } 192 | 193 | test_MavenDeployLibraryTest_ReleaseSucceeds() { 194 | cd "${TEST_CASE_TMPDIR}" 195 | create_maven_folder_and_pom_file 196 | 197 | cat << EOF > expected_commands.txt 198 | bazel build tink.jar tink-src.jar tink-javadoc.jar 199 | mvn gpg:sign-and-deploy-file -DrepositoryId=ossrh \ 200 | -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ \ 201 | -Dgpg.keyname=tink-dev@google.com \ 202 | --settings=${TEST_CASE_TMPDIR}/${RELATIVE_MVN_DIR_PATH}/settings.xml \ 203 | -Dfile=${TEST_CASE_TMPDIR}/bazel-bin/tink.jar \ 204 | -Dsources=${TEST_CASE_TMPDIR}/bazel-bin/tink-src.jar \ 205 | -Djavadoc=${TEST_CASE_TMPDIR}/bazel-bin/tink-javadoc.jar \ 206 | -DpomFile=${RELATIVE_MVN_DIR_PATH}/tink.pom.xml 207 | EOF 208 | 209 | bazel() { 210 | echo "bazel $@" >> actual_commands.txt 211 | mock_bazel "$@" 212 | } 213 | 214 | bazelisk() { 215 | bazel "$@" 216 | } 217 | 218 | git() { 219 | mkdir -p gh-pages 220 | } 221 | 222 | mvn() { 223 | echo "mvn $@" >> actual_commands.txt 224 | local -r repo_url="https://oss.sonatype.org/service/local/staging/deploy/maven2/" 225 | # Make sure the correct URL is set, if not, return. This is to guarantee the 226 | # parameter is replaced with the local URI below. 227 | if ! echo "$@" | grep "${repo_url}" > /dev/null; then 228 | echo "URL not found" >&2 229 | return 1 230 | fi 231 | params=($(echo "$@" | sed 's#'${repo_url}'#file://'${TEST_CASE_TMPDIR}'#g' \ 232 | | sed 's#tink-dev@google.com#test@test.com#g')) 233 | 234 | export SONATYPE_USERNAME="some_username" 235 | export SONATYPE_PASSWORD="some_password" 236 | export TINK_DEV_MAVEN_PGP_PASSPHRASE="This-is-a-passphrase" 237 | 238 | # If the actual mvn command is set, we run it. 239 | if [[ -n "${ACTUAL_MVN_CMD}" ]]; then 240 | create_test_gpg_key || return 1 241 | "${ACTUAL_MVN_CMD}" "${params[@]}" || return 1 242 | delete_gpg_test_key || return 1 243 | fi 244 | } 245 | 246 | ( 247 | source "${CLI}" -u github.com/tink-crypto/tink-java release tink \ 248 | "${RELATIVE_MVN_DIR_PATH}/tink.pom.xml" 1.2.3 > /dev/null 249 | ) 250 | ASSERT_CMD_SUCCEEDED 251 | ASSERT_FILE_EQUALS actual_commands.txt expected_commands.txt 252 | } 253 | 254 | test_MavenDeployLibraryTest_SnapshotSucceeds() { 255 | cd "${TEST_CASE_TMPDIR}" 256 | create_maven_folder_and_pom_file 257 | 258 | cat << EOF > expected_commands.txt 259 | bazel build tink.jar tink-src.jar tink-javadoc.jar 260 | mvn deploy:deploy-file -DrepositoryId=ossrh \ 261 | -Durl=https://oss.sonatype.org/content/repositories/snapshots \ 262 | --settings=${TEST_CASE_TMPDIR}/${RELATIVE_MVN_DIR_PATH}/settings.xml \ 263 | -Dfile=${TEST_CASE_TMPDIR}/bazel-bin/tink.jar \ 264 | -Dsources=${TEST_CASE_TMPDIR}/bazel-bin/tink-src.jar \ 265 | -Djavadoc=${TEST_CASE_TMPDIR}/bazel-bin/tink-javadoc.jar \ 266 | -DpomFile=${RELATIVE_MVN_DIR_PATH}/tink.pom.xml 267 | EOF 268 | 269 | bazel() { 270 | echo "bazel $@" >> actual_commands.txt 271 | mock_bazel "$@" 272 | } 273 | 274 | bazelisk() { 275 | bazel "$@" 276 | } 277 | 278 | git() { 279 | mkdir -p gh-pages 280 | } 281 | 282 | mvn() { 283 | echo "mvn $@" >> actual_commands.txt 284 | } 285 | 286 | ( 287 | source "${CLI}" -u github.com/tink-crypto/tink-java snapshot tink \ 288 | "${RELATIVE_MVN_DIR_PATH}/tink.pom.xml" HEAD > /dev/null 289 | ) 290 | ASSERT_CMD_SUCCEEDED 291 | ASSERT_FILE_EQUALS actual_commands.txt expected_commands.txt 292 | } 293 | 294 | main() { 295 | run_all_tests "$@" 296 | } 297 | 298 | main "$@" 299 | -------------------------------------------------------------------------------- /maven/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.SONATYPE_USERNAME} 6 | ${env.SONATYPE_PASSWORD} 7 | 8 | 9 | gpg.passphrase 10 | ${env.TINK_DEV_MAVEN_PGP_PASSPHRASE} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /maven/tink-java-apps-paymentmethodtoken.pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 4.0.0 22 | Tink Cryptography API for Google Payment Method Token 23 | An implementation of Google Payment Method Token (https://developers.google.com/android-pay/integration/payment-token-cryptography), using Tink (https://github.com/tink-crypto/tink-java). 24 | 25 | 26 | 27 | org.sonatype.oss 28 | oss-parent 29 | 7 30 | 31 | 32 | com.google.crypto.tink 33 | apps-paymentmethodtoken 34 | VERSION_PLACEHOLDER 35 | jar 36 | http://github.com/tink-crypto/tink-java-apps 37 | 38 | 39 | 40 | Apache License, Version 2.0 41 | http://www.apache.org/licenses/LICENSE-2.0.txt 42 | repo 43 | 44 | 45 | 46 | 47 | 48 | ossrh 49 | https://oss.sonatype.org/content/repositories/snapshots 50 | 51 | 52 | ossrh 53 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 54 | 55 | 56 | 57 | 58 | GitHub 59 | https://github.com/tink-crypto/tink-java-apps/issues 60 | 61 | 62 | 63 | 64 | tink-users 65 | tink-users+subscribe@googlegroups.com 66 | tink-users+unsubscribe@googlegroups.com 67 | tink-users@googlegroups.com 68 | https://groups.google.com/group/tink-users 69 | 70 | 71 | 72 | 73 | 74 | Google LLC 75 | https://www.google.com 76 | 77 | 78 | 79 | 80 | scm:git:git@github.com:tink-crypto/tink-java-apps.git 81 | scm:git:git@github.com:tink-crypto/tink-java-apps.git 82 | https://github.com/tink-crypto/tink-java-apps.git 83 | HEAD 84 | 85 | 86 | 87 | 1.8 88 | 2.22.0 89 | 1.46.3 90 | 2.10.1 91 | 1.16.0 92 | 93 | 94 | 95 | 96 | com.google.errorprone 97 | error_prone_annotations 98 | ${error_prone_annotations.version} 99 | 100 | 101 | com.google.http-client 102 | google-http-client 103 | ${google-http-client.version} 104 | 105 | 106 | com.google.crypto.tink 107 | tink 108 | ${tink.version} 109 | 110 | 111 | com.google.code.gson 112 | gson 113 | ${gson.version} 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /maven/tink-java-apps-rewardedads.pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 4.0.0 22 | Tink Cryptography API for Google Mobile Rewarded Video Ads SSV 23 | An implementation of the verifier side of Server-Side Verification of Google mobile rewarded video ads, using Tink (https://github.com/tink-crypto/tink-java). 24 | 25 | 26 | 27 | org.sonatype.oss 28 | oss-parent 29 | 7 30 | 31 | 32 | com.google.crypto.tink 33 | apps-rewardedads 34 | VERSION_PLACEHOLDER 35 | jar 36 | http://github.com/tink-crypto/tink-java-apps 37 | 38 | 39 | 40 | Apache License, Version 2.0 41 | http://www.apache.org/licenses/LICENSE-2.0.txt 42 | repo 43 | 44 | 45 | 46 | 47 | 48 | ossrh 49 | https://oss.sonatype.org/content/repositories/snapshots 50 | 51 | 52 | ossrh 53 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 54 | 55 | 56 | 57 | 58 | GitHub 59 | https://github.com/tink-crypto/tink-java-apps/issues 60 | 61 | 62 | 63 | 64 | tink-users 65 | tink-users+subscribe@googlegroups.com 66 | tink-users+unsubscribe@googlegroups.com 67 | tink-users@googlegroups.com 68 | https://groups.google.com/group/tink-users 69 | 70 | 71 | 72 | 73 | 74 | Google LLC 75 | https://www.google.com 76 | 77 | 78 | 79 | 80 | scm:git:git@github.com:tink-crypto/tink-java-apps.git 81 | scm:git:git@github.com:tink-crypto/tink-java-apps.git 82 | https://github.com/tink-crypto/tink-java-apps.git 83 | HEAD 84 | 85 | 86 | 87 | 1.8 88 | 2.22.0 89 | 1.46.3 90 | 2.10.1 91 | 1.16.0 92 | 93 | 94 | 95 | 96 | com.google.errorprone 97 | error_prone_annotations 98 | ${error_prone_annotations.version} 99 | 100 | 101 | com.google.http-client 102 | google-http-client 103 | ${google-http-client.version} 104 | 105 | 106 | com.google.crypto.tink 107 | tink 108 | ${tink.version} 109 | 110 | 111 | com.google.code.gson 112 | gson 113 | ${gson.version} 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /maven/tink-java-apps-webpush.pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 4.0.0 22 | Tink Cryptography API for Message Encryption for Web Push (RFC 8291) 23 | An implementation of Message Encryption for Web Push (RFC 8291), using Tink (https://github.com/tink-crypto/tink-java). 24 | 25 | 26 | 27 | org.sonatype.oss 28 | oss-parent 29 | 7 30 | 31 | 32 | com.google.crypto.tink 33 | apps-webpush 34 | VERSION_PLACEHOLDER 35 | jar 36 | http://github.com/tink-crypto/tink-java-apps 37 | 38 | 39 | 40 | Apache License, Version 2.0 41 | http://www.apache.org/licenses/LICENSE-2.0.txt 42 | repo 43 | 44 | 45 | 46 | 47 | 48 | ossrh 49 | https://oss.sonatype.org/content/repositories/snapshots 50 | 51 | 52 | ossrh 53 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 54 | 55 | 56 | 57 | 58 | GitHub 59 | https://github.com/tink-crypto/tink-java-apps/issues 60 | 61 | 62 | 63 | 64 | tink-users 65 | tink-users+subscribe@googlegroups.com 66 | tink-users+unsubscribe@googlegroups.com 67 | tink-users@googlegroups.com 68 | https://groups.google.com/group/tink-users 69 | 70 | 71 | 72 | 73 | 74 | Google LLC 75 | https://www.google.com 76 | 77 | 78 | 79 | 80 | scm:git:git@github.com:tink-crypto/tink-java-apps.git 81 | scm:git:git@github.com:tink-crypto/tink-java-apps.git 82 | https://github.com/tink-crypto/tink-java-apps.git 83 | HEAD 84 | 85 | 86 | 87 | 1.8 88 | 2.22.0 89 | 1.16.0 90 | 91 | 92 | 93 | 94 | com.google.errorprone 95 | error_prone_annotations 96 | ${error_prone_annotations.version} 97 | 98 | 99 | com.google.crypto.tink 100 | tink 101 | ${tink.version} 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /paymentmethodtoken/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) 6 | 7 | gen_maven_jar_rules( 8 | name = "maven", 9 | doctitle = "Tink Cryptography API for Google Payment Method Token", 10 | manifest_lines = [ 11 | "Automatic-Module-Name: com.google.crypto.tink.apps.paymentmethodtoken", 12 | ], 13 | root_packages = ["com.google.crypto.tink.apps.paymentmethodtoken"], 14 | deps = [ 15 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:google_payments_public_keys_manager", 16 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:jwt_key_converter", 17 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:keys_downloader", 18 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_constants", 19 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_decrypt", 20 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_encrypt", 21 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient", 22 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_kem", 23 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_key_gen", 24 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_sender", 25 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_util", 26 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:sender_intermediate_cert_factory", 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | licenses(["notice"]) 4 | 5 | java_library( 6 | name = "google_payments_public_keys_manager", 7 | srcs = ["GooglePaymentsPublicKeysManager.java"], 8 | deps = [ 9 | ":keys_downloader", 10 | "@maven//:com_google_errorprone_error_prone_annotations", 11 | "@maven//:com_google_http_client_google_http_client", 12 | ], 13 | ) 14 | 15 | java_library( 16 | name = "payment_method_token_hybrid_decrypt", 17 | srcs = ["PaymentMethodTokenHybridDecrypt.java"], 18 | deps = [ 19 | ":payment_method_token_constants", 20 | ":payment_method_token_recipient_kem", 21 | ":payment_method_token_util", 22 | "@maven//:com_google_code_gson_gson", 23 | "@maven//:com_google_crypto_tink_tink", 24 | ], 25 | ) 26 | 27 | java_library( 28 | name = "payment_method_token_sender", 29 | srcs = ["PaymentMethodTokenSender.java"], 30 | deps = [ 31 | ":payment_method_token_constants", 32 | ":payment_method_token_hybrid_encrypt", 33 | ":payment_method_token_util", 34 | "@maven//:com_google_code_gson_gson", 35 | "@maven//:com_google_crypto_tink_tink", 36 | "@maven//:com_google_errorprone_error_prone_annotations", 37 | ], 38 | ) 39 | 40 | java_library( 41 | name = "payment_method_token_recipient_key_gen", 42 | srcs = ["PaymentMethodTokenRecipientKeyGen.java"], 43 | deps = [ 44 | ":payment_method_token_constants", 45 | "@maven//:com_google_crypto_tink_tink", 46 | ], 47 | ) 48 | 49 | java_library( 50 | name = "payment_method_token_constants", 51 | srcs = ["PaymentMethodTokenConstants.java"], 52 | deps = [ 53 | "@maven//:com_google_crypto_tink_tink", 54 | ], 55 | ) 56 | 57 | java_library( 58 | name = "payment_method_token_recipient_kem", 59 | srcs = ["PaymentMethodTokenRecipientKem.java"], 60 | ) 61 | 62 | java_library( 63 | name = "payment_method_token_hybrid_encrypt", 64 | srcs = ["PaymentMethodTokenHybridEncrypt.java"], 65 | deps = [ 66 | ":payment_method_token_constants", 67 | ":payment_method_token_util", 68 | "@maven//:com_google_code_gson_gson", 69 | "@maven//:com_google_crypto_tink_tink", 70 | ], 71 | ) 72 | 73 | java_library( 74 | name = "payment_method_token_recipient", 75 | srcs = ["PaymentMethodTokenRecipient.java"], 76 | deps = [ 77 | ":google_payments_public_keys_manager", 78 | ":payment_method_token_constants", 79 | ":payment_method_token_hybrid_decrypt", 80 | ":payment_method_token_recipient_kem", 81 | ":payment_method_token_util", 82 | "@maven//:com_google_code_gson_gson", 83 | "@maven//:com_google_crypto_tink_tink", 84 | "@maven//:com_google_errorprone_error_prone_annotations", 85 | ], 86 | ) 87 | 88 | java_library( 89 | name = "sender_intermediate_cert_factory", 90 | srcs = ["SenderIntermediateCertFactory.java"], 91 | deps = [ 92 | ":payment_method_token_constants", 93 | ":payment_method_token_util", 94 | "@maven//:com_google_code_gson_gson", 95 | "@maven//:com_google_crypto_tink_tink", 96 | "@maven//:com_google_errorprone_error_prone_annotations", 97 | ], 98 | ) 99 | 100 | java_library( 101 | name = "payment_method_token_util", 102 | srcs = ["PaymentMethodTokenUtil.java"], 103 | deps = [ 104 | ":payment_method_token_constants", 105 | "@maven//:com_google_crypto_tink_tink", 106 | ], 107 | ) 108 | 109 | java_library( 110 | name = "jwt_key_converter", 111 | srcs = ["JwtKeyConverter.java"], 112 | deps = [ 113 | "@maven//:com_google_crypto_tink_tink", 114 | ], 115 | ) 116 | 117 | java_library( 118 | name = "keys_downloader", 119 | srcs = ["KeysDownloader.java"], 120 | deps = [ 121 | "@maven//:androidx_annotation_annotation_jvm", 122 | "@maven//:com_google_code_findbugs_jsr305", 123 | "@maven//:com_google_crypto_tink_tink", 124 | "@maven//:com_google_errorprone_error_prone_annotations", 125 | "@maven//:com_google_http_client_google_http_client", 126 | ], 127 | ) 128 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import com.google.api.client.http.HttpTransport; 20 | import com.google.api.client.http.javanet.NetHttpTransport; 21 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 22 | import java.io.IOException; 23 | import java.util.Arrays; 24 | import java.util.concurrent.Executor; 25 | import java.util.concurrent.Executors; 26 | 27 | /** 28 | * Thread-safe Google Payments public key manager. 29 | * 30 | *

For best performance, use the {@link GooglePaymentsPublicKeysManager#INSTANCE_PRODUCTION} for 31 | * production environment or {@link GooglePaymentsPublicKeysManager#INSTANCE_TEST} for test 32 | * environment. 33 | * 34 | *

If you need extra customizations for your use, we recommend you to use {@link 35 | * GooglePaymentsPublicKeysManager.Builder} to construct an instance and keep it as a singleton in a 36 | * static final variable across requests. 37 | * 38 | *

When initializing your server, we also recommend that you call {@link #refreshInBackground()} 39 | * to proactively fetch the keys. 40 | * 41 | * @since 1.0.0 42 | */ 43 | public class GooglePaymentsPublicKeysManager { 44 | /** Default HTTP transport used by this class. */ 45 | public static final NetHttpTransport DEFAULT_HTTP_TRANSPORT = 46 | new NetHttpTransport.Builder().build(); 47 | /** URL to fetch keys for environment production. */ 48 | public static final String KEYS_URL_PRODUCTION = 49 | "https://payments.developers.google.com/paymentmethodtoken/keys.json"; 50 | /** URL to fetch keys for environment test. */ 51 | public static final String KEYS_URL_TEST = 52 | "https://payments.developers.google.com/paymentmethodtoken/test/keys.json"; 53 | 54 | private static final Executor DEFAULT_BACKGROUND_EXECUTOR = Executors.newCachedThreadPool(); 55 | 56 | private final KeysDownloader downloader; 57 | 58 | /** 59 | * Instance configured to talk to fetch keys from production environment (from {@link 60 | * GooglePaymentsPublicKeysManager#KEYS_URL_PRODUCTION}). 61 | */ 62 | public static final GooglePaymentsPublicKeysManager INSTANCE_PRODUCTION = 63 | new GooglePaymentsPublicKeysManager( 64 | DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_PRODUCTION); 65 | /** 66 | * Instance configured to talk to fetch keys from test environment (from {@link 67 | * GooglePaymentsPublicKeysManager#KEYS_URL_TEST}). 68 | */ 69 | public static final GooglePaymentsPublicKeysManager INSTANCE_TEST = 70 | new GooglePaymentsPublicKeysManager( 71 | DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_TEST); 72 | 73 | GooglePaymentsPublicKeysManager( 74 | Executor backgroundExecutor, HttpTransport httpTransport, String keysUrl) { 75 | this.downloader = 76 | new KeysDownloader.Builder() 77 | .setUrl(keysUrl) 78 | .setExecutor(backgroundExecutor) 79 | .setHttpTransport(httpTransport) 80 | .build(); 81 | } 82 | 83 | HttpTransport getHttpTransport() { 84 | return downloader.getHttpTransport(); 85 | } 86 | 87 | String getUrl() { 88 | return downloader.getUrl(); 89 | } 90 | 91 | /** 92 | * Returns a string containing a JSON with the Google public signing keys. 93 | * 94 | *

Meant to be called by {@link PaymentMethodTokenRecipient}. 95 | */ 96 | String getTrustedSigningKeysJson() throws IOException { 97 | return this.downloader.download(); 98 | } 99 | 100 | /** Fetches keys in the background. */ 101 | public void refreshInBackground() { 102 | downloader.refreshInBackground(); 103 | } 104 | 105 | /** 106 | * Builder for {@link GooglePaymentsPublicKeysManager}. 107 | * 108 | * @since 1.0.0 109 | */ 110 | public static class Builder { 111 | private HttpTransport httpTransport = DEFAULT_HTTP_TRANSPORT; 112 | private String keysUrl = KEYS_URL_PRODUCTION; 113 | 114 | @CanIgnoreReturnValue 115 | public Builder setKeysUrl(String keysUrl) { 116 | this.keysUrl = keysUrl; 117 | return this; 118 | } 119 | 120 | /** 121 | * Sets the HTTP transport. 122 | * 123 | *

You generally should not need to set a custom transport as the default transport {@link 124 | * GooglePaymentsPublicKeysManager#DEFAULT_HTTP_TRANSPORT} should be suited for most use cases. 125 | */ 126 | @CanIgnoreReturnValue 127 | public Builder setHttpTransport(HttpTransport httpTransport) { 128 | this.httpTransport = httpTransport; 129 | return this; 130 | } 131 | 132 | public GooglePaymentsPublicKeysManager build() { 133 | // If all parameters are equal to the existing singleton instances, returning them instead. 134 | // This is more a safe guard if users of this class construct a new class and forget to 135 | // save in a singleton. 136 | for (GooglePaymentsPublicKeysManager instance : 137 | Arrays.asList(INSTANCE_PRODUCTION, INSTANCE_TEST)) { 138 | if (instance.getHttpTransport() == httpTransport && instance.getUrl().equals(keysUrl)) { 139 | return instance; 140 | } 141 | } 142 | return new GooglePaymentsPublicKeysManager( 143 | DEFAULT_BACKGROUND_EXECUTOR, httpTransport, keysUrl); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/JwtKeyConverter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.AccessesPartialKey; 20 | import com.google.crypto.tink.SecretKeyAccess; 21 | import com.google.crypto.tink.jwt.JwtEcdsaParameters; 22 | import com.google.crypto.tink.jwt.JwtEcdsaPrivateKey; 23 | import com.google.crypto.tink.jwt.JwtEcdsaPublicKey; 24 | import com.google.crypto.tink.subtle.Base64; 25 | import com.google.crypto.tink.subtle.EllipticCurves; 26 | import com.google.crypto.tink.util.SecretBigInteger; 27 | import java.security.GeneralSecurityException; 28 | import java.security.interfaces.ECPrivateKey; 29 | import java.security.interfaces.ECPublicKey; 30 | 31 | /** 32 | * Functions that convert raw keys into Tink JWT Signature keys. 33 | * 34 | *

These functions are currently just an example, and are not (yet) part of the public API. 35 | */ 36 | public final class JwtKeyConverter { 37 | 38 | /** 39 | * Converts an uncompressed Base64 encoded ECDSA public key for NIST P-256 Curve into a Tink 40 | * JwtEcdsaPublicKey with algorithm ES256. 41 | */ 42 | @AccessesPartialKey 43 | public static JwtEcdsaPublicKey fromBase64EncodedNistP256PublicKey( 44 | String based64EncodedEcNistP256PublicKey) throws GeneralSecurityException { 45 | ECPublicKey ecPublicKey = 46 | EllipticCurves.getEcPublicKey( 47 | EllipticCurves.CurveType.NIST_P256, 48 | EllipticCurves.PointFormatType.UNCOMPRESSED, 49 | Base64.decode(based64EncodedEcNistP256PublicKey)); 50 | JwtEcdsaParameters parameters = 51 | JwtEcdsaParameters.builder() 52 | .setKidStrategy(JwtEcdsaParameters.KidStrategy.IGNORED) 53 | .setAlgorithm(JwtEcdsaParameters.Algorithm.ES256) 54 | .build(); 55 | return JwtEcdsaPublicKey.builder() 56 | .setParameters(parameters) 57 | .setPublicPoint(ecPublicKey.getW()) 58 | .build(); 59 | } 60 | 61 | /** 62 | * Converts a JwtEcdsaPublicKey using algorithm ES256 to an uncompressed Base64 encoded ECDSA 63 | * public key for NIST P-256 Curve. 64 | */ 65 | @AccessesPartialKey 66 | public static String toBase64EncodedNistP256PublicKey(JwtEcdsaPublicKey publicKey) 67 | throws GeneralSecurityException { 68 | if (publicKey.getParameters().getAlgorithm() != JwtEcdsaParameters.Algorithm.ES256) { 69 | throw new GeneralSecurityException("Only ES256 is supported."); 70 | } 71 | if (publicKey.getParameters().getKidStrategy() != JwtEcdsaParameters.KidStrategy.IGNORED) { 72 | throw new GeneralSecurityException("Only KidStrategy IGNORED is supported."); 73 | } 74 | return Base64.encode( 75 | EllipticCurves.pointEncode( 76 | EllipticCurves.CurveType.NIST_P256, 77 | EllipticCurves.PointFormatType.UNCOMPRESSED, 78 | publicKey.getPublicPoint())); 79 | } 80 | 81 | /** 82 | * Converts a Base64 encoded PKCS8 ECDSA private key for NIST P-256 Curve into a Tink 83 | * JwtEcdsaPrivateKey with algorithm ES256. 84 | * 85 | *

It also requires that you provide the corresponding JwtEcdsaPublicKey. 86 | */ 87 | @AccessesPartialKey 88 | public static JwtEcdsaPrivateKey fromBased64EncodedPkcs8EcNistP256PrivateKey( 89 | String based64EncodedPkcs8EcNistP256PrivateKey, 90 | JwtEcdsaPublicKey publicKey, 91 | SecretKeyAccess access) 92 | throws GeneralSecurityException { 93 | if (publicKey.getParameters().getAlgorithm() != JwtEcdsaParameters.Algorithm.ES256) { 94 | throw new GeneralSecurityException("Only ES256 is supported."); 95 | } 96 | if (publicKey.getParameters().getKidStrategy() != JwtEcdsaParameters.KidStrategy.IGNORED) { 97 | throw new GeneralSecurityException("Only KidStrategy IGNORED is supported."); 98 | } 99 | ECPrivateKey ecPrivateKey = 100 | EllipticCurves.getEcPrivateKey(Base64.decode(based64EncodedPkcs8EcNistP256PrivateKey)); 101 | return JwtEcdsaPrivateKey.create( 102 | publicKey, SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), access)); 103 | } 104 | 105 | private JwtKeyConverter() {} 106 | } 107 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.subtle.EllipticCurves; 20 | import com.google.crypto.tink.subtle.Enums.HashType; 21 | import java.nio.charset.StandardCharsets; 22 | 23 | /** Various constants. */ 24 | final class PaymentMethodTokenConstants { 25 | public static final String GOOGLE_SENDER_ID = "Google"; 26 | public static final String HMAC_SHA256_ALGO = "HmacSha256"; 27 | public static final byte[] HKDF_EMPTY_SALT = new byte[0]; 28 | public static final byte[] GOOGLE_CONTEXT_INFO_ECV1 = "Google".getBytes(StandardCharsets.UTF_8); 29 | public static final String AES_CTR_ALGO = "AES/CTR/NoPadding"; 30 | // Zero IV is fine here because each encryption uses a unique key. 31 | public static final byte[] AES_CTR_ZERO_IV = new byte[16]; 32 | public static final EllipticCurves.CurveType P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256; 33 | public static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT = 34 | EllipticCurves.PointFormatType.UNCOMPRESSED; 35 | public static final String PROTOCOL_VERSION_EC_V1 = "ECv1"; 36 | public static final String PROTOCOL_VERSION_EC_V2 = "ECv2"; 37 | public static final String PROTOCOL_VERSION_EC_V2_SIGNING_ONLY = "ECv2SigningOnly"; 38 | public static final HashType ECDSA_HASH_SHA256 = HashType.SHA256; 39 | 40 | public static final String JSON_ENCRYPTED_MESSAGE_KEY = "encryptedMessage"; 41 | public static final String JSON_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey"; 42 | public static final String JSON_INTERMEDIATE_SIGNING_KEY = "intermediateSigningKey"; 43 | public static final String JSON_KEY_EXPIRATION_KEY = "keyExpiration"; 44 | public static final String JSON_KEY_VALUE_KEY = "keyValue"; 45 | public static final String JSON_MESSAGE_EXPIRATION_KEY = "messageExpiration"; 46 | public static final String JSON_PROTOCOL_VERSION_KEY = "protocolVersion"; 47 | public static final String JSON_SIGNATURES_KEY = "signatures"; 48 | public static final String JSON_SIGNATURE_KEY = "signature"; 49 | public static final String JSON_SIGNED_KEY_KEY = "signedKey"; 50 | public static final String JSON_SIGNED_MESSAGE_KEY = "signedMessage"; 51 | public static final String JSON_TAG_KEY = "tag"; 52 | 53 | /** Represents configuration regarding each protocol version. */ 54 | enum ProtocolVersionConfig { 55 | EC_V1( 56 | /* protocolVersion= */ PROTOCOL_VERSION_EC_V1, 57 | /* aesCtrKeySize= */ 128 / 8, 58 | /* hmacSha256KeySize= */ 128 / 8, 59 | /* isEncryptionRequired= */ true, 60 | /* supportsIntermediateSigningKeys= */ false), 61 | EC_V2( 62 | /* protocolVersion= */ PROTOCOL_VERSION_EC_V2, 63 | /* aesCtrKeySize= */ 256 / 8, 64 | /* hmacSha256KeySize= */ 256 / 8, 65 | /* isEncryptionRequired= */ true, 66 | /* supportsIntermediateSigningKeys= */ true), 67 | EC_V2_SIGNING_ONLY( 68 | /* protocolVersion= */ PROTOCOL_VERSION_EC_V2_SIGNING_ONLY, 69 | /* aesCtrKeySize= */ 256 / 8, 70 | /* hmacSha256KeySize= */ 256 / 8, 71 | /* isEncryptionRequired= */ false, 72 | /* supportsIntermediateSigningKeys= */ true); 73 | 74 | public final String protocolVersion; 75 | public final int aesCtrKeySize; 76 | public final int hmacSha256KeySize; 77 | public final boolean isEncryptionRequired; 78 | public final boolean supportsIntermediateSigningKeys; 79 | 80 | ProtocolVersionConfig( 81 | String protocolVersion, 82 | int aesCtrKeySize, 83 | int hmacSha256KeySize, 84 | boolean isEncryptionRequired, 85 | boolean supportsIntermediateSigningKeys) { 86 | this.protocolVersion = protocolVersion; 87 | this.aesCtrKeySize = aesCtrKeySize; 88 | this.hmacSha256KeySize = hmacSha256KeySize; 89 | this.isEncryptionRequired = isEncryptionRequired; 90 | this.supportsIntermediateSigningKeys = supportsIntermediateSigningKeys; 91 | } 92 | 93 | public static ProtocolVersionConfig forProtocolVersion(String protocolVersion) { 94 | for (ProtocolVersionConfig protocolVersionConfig : ProtocolVersionConfig.values()) { 95 | if (protocolVersionConfig.protocolVersion.equals(protocolVersion)) { 96 | return protocolVersionConfig; 97 | } 98 | } 99 | throw new IllegalArgumentException("Unknown protocol version: " + protocolVersion); 100 | } 101 | } 102 | 103 | private PaymentMethodTokenConstants() {} 104 | } 105 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static java.nio.charset.StandardCharsets.UTF_8; 20 | 21 | import com.google.crypto.tink.HybridDecrypt; 22 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 23 | import com.google.crypto.tink.subtle.Base64; 24 | import com.google.crypto.tink.subtle.Bytes; 25 | import com.google.crypto.tink.subtle.EllipticCurves; 26 | import com.google.crypto.tink.subtle.Hkdf; 27 | import com.google.gson.JsonObject; 28 | import com.google.gson.JsonParseException; 29 | import com.google.gson.JsonParser; 30 | import java.security.GeneralSecurityException; 31 | import java.security.interfaces.ECPrivateKey; 32 | import java.security.interfaces.ECPublicKey; 33 | import java.util.Arrays; 34 | 35 | /** 36 | * A {@link HybridDecrypt} implementation for the hybrid encryption used in Google Payment Method 38 | * Token. 39 | */ 40 | class PaymentMethodTokenHybridDecrypt implements HybridDecrypt { 41 | private final PaymentMethodTokenRecipientKem recipientKem; 42 | private final ProtocolVersionConfig protocolVersionConfig; 43 | 44 | PaymentMethodTokenHybridDecrypt( 45 | final ECPrivateKey recipientPrivateKey, ProtocolVersionConfig protocolVersionConfig) 46 | throws GeneralSecurityException { 47 | this( 48 | new PaymentMethodTokenRecipientKem() { 49 | @Override 50 | public byte[] computeSharedSecret(final byte[] ephemeralPublicKey) 51 | throws GeneralSecurityException { 52 | ECPublicKey publicKey = 53 | EllipticCurves.getEcPublicKey( 54 | recipientPrivateKey.getParams(), 55 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT, 56 | ephemeralPublicKey); 57 | return EllipticCurves.computeSharedSecret(recipientPrivateKey, publicKey); 58 | } 59 | }, 60 | protocolVersionConfig); 61 | } 62 | 63 | PaymentMethodTokenHybridDecrypt( 64 | final PaymentMethodTokenRecipientKem recipientKem, 65 | ProtocolVersionConfig protocolVersionConfig) { 66 | this.recipientKem = recipientKem; 67 | this.protocolVersionConfig = protocolVersionConfig; 68 | } 69 | 70 | @Override 71 | public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo) 72 | throws GeneralSecurityException { 73 | try { 74 | JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject(); 75 | validate(json); 76 | byte[] demKey = kem(json, contextInfo); 77 | return dem(json, demKey); 78 | } catch (JsonParseException | IllegalStateException e) { 79 | throw new GeneralSecurityException("cannot decrypt; failed to parse JSON", e); 80 | } 81 | } 82 | 83 | private byte[] kem(JsonObject json, final byte[] contextInfo) throws GeneralSecurityException { 84 | int demKeySize = protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize; 85 | byte[] ephemeralPublicKey = 86 | Base64.decode( 87 | json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString()); 88 | byte[] sharedSecret = recipientKem.computeSharedSecret(ephemeralPublicKey); 89 | return Hkdf.computeEciesHkdfSymmetricKey( 90 | ephemeralPublicKey, 91 | sharedSecret, 92 | PaymentMethodTokenConstants.HMAC_SHA256_ALGO, 93 | PaymentMethodTokenConstants.HKDF_EMPTY_SALT, 94 | contextInfo, 95 | demKeySize); 96 | } 97 | 98 | private byte[] dem(JsonObject json, final byte[] demKey) throws GeneralSecurityException { 99 | byte[] hmacSha256Key = 100 | Arrays.copyOfRange(demKey, protocolVersionConfig.aesCtrKeySize, demKey.length); 101 | byte[] encryptedMessage = 102 | Base64.decode( 103 | json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString()); 104 | byte[] computedTag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, encryptedMessage); 105 | byte[] expectedTag = 106 | Base64.decode(json.get(PaymentMethodTokenConstants.JSON_TAG_KEY).getAsString()); 107 | if (!Bytes.equal(expectedTag, computedTag)) { 108 | throw new GeneralSecurityException("cannot decrypt; invalid MAC"); 109 | } 110 | byte[] aesCtrKey = Arrays.copyOf(demKey, protocolVersionConfig.aesCtrKeySize); 111 | return PaymentMethodTokenUtil.aesCtr(aesCtrKey, encryptedMessage); 112 | } 113 | 114 | private void validate(JsonObject payload) throws GeneralSecurityException { 115 | if (!payload.has(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY) 116 | || !payload.has(PaymentMethodTokenConstants.JSON_TAG_KEY) 117 | || !payload.has(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY) 118 | || payload.size() != 3) { 119 | throw new GeneralSecurityException( 120 | "The payload must contain exactly encryptedMessage, tag and ephemeralPublicKey"); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static java.nio.charset.StandardCharsets.UTF_8; 20 | 21 | import com.google.crypto.tink.HybridEncrypt; 22 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 23 | import com.google.crypto.tink.subtle.Base64; 24 | import com.google.crypto.tink.subtle.EciesHkdfSenderKem; 25 | import com.google.gson.JsonObject; 26 | import com.google.gson.internal.Streams; 27 | import com.google.gson.stream.JsonWriter; 28 | import java.io.IOException; 29 | import java.io.StringWriter; 30 | import java.security.GeneralSecurityException; 31 | import java.security.interfaces.ECPublicKey; 32 | import java.util.Arrays; 33 | 34 | /** 35 | * A {@link HybridEncrypt} implementation for the hybrid encryption used in Google Payment Method 37 | * Token. 38 | */ 39 | class PaymentMethodTokenHybridEncrypt implements HybridEncrypt { 40 | private final EciesHkdfSenderKem senderKem; 41 | private final ProtocolVersionConfig protocolVersionConfig; 42 | 43 | public PaymentMethodTokenHybridEncrypt( 44 | final ECPublicKey recipientPublicKey, final ProtocolVersionConfig protocolVersionConfig) { 45 | this.senderKem = new EciesHkdfSenderKem(recipientPublicKey); 46 | this.protocolVersionConfig = protocolVersionConfig; 47 | } 48 | 49 | static String jsonEncodeCiphertext(byte[] ciphertext, byte[] tag, byte[] ephemeralPublicKey) 50 | throws GeneralSecurityException { 51 | JsonObject result = new JsonObject(); 52 | result.addProperty( 53 | PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(ciphertext)); 54 | result.addProperty( 55 | PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY, Base64.encode(ephemeralPublicKey)); 56 | result.addProperty(PaymentMethodTokenConstants.JSON_TAG_KEY, Base64.encode(tag)); 57 | StringWriter stringWriter = new StringWriter(); 58 | JsonWriter jsonWriter = new JsonWriter(stringWriter); 59 | jsonWriter.setHtmlSafe(true); 60 | try { 61 | Streams.write(result, jsonWriter); 62 | return stringWriter.toString(); 63 | } catch (IOException e) { 64 | throw new GeneralSecurityException("cannot encrypt; JSON error", e); 65 | } 66 | } 67 | 68 | @Override 69 | public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo) 70 | throws GeneralSecurityException { 71 | int symmetricKeySize = 72 | protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize; 73 | EciesHkdfSenderKem.KemKey kemKey = 74 | senderKem.generateKey( 75 | PaymentMethodTokenConstants.HMAC_SHA256_ALGO, 76 | PaymentMethodTokenConstants.HKDF_EMPTY_SALT, 77 | contextInfo, 78 | symmetricKeySize, 79 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT); 80 | byte[] aesCtrKey = Arrays.copyOf(kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize); 81 | byte[] ciphertext = PaymentMethodTokenUtil.aesCtr(aesCtrKey, plaintext); 82 | byte[] hmacSha256Key = 83 | Arrays.copyOfRange( 84 | kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize, symmetricKeySize); 85 | byte[] tag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, ciphertext); 86 | byte[] ephemeralPublicKey = kemKey.getKemBytes(); 87 | 88 | String jsonEncodedCiphertext = jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey); 89 | return jsonEncodedCiphertext.getBytes(UTF_8); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import java.security.GeneralSecurityException; 20 | 21 | /** 22 | * Interface for recipient's key encapsulation mechanism (KEM). 23 | * 24 | *

Google Pay's tokens are encrypted using ECIES which is a hybrid encryption mode consisting of 25 | * two steps: key encapsulation mechanisam (KEM) using Elliptic Curve Diffie Hellman (ECDH) and HKDF 26 | * and data encapsulation mechanism (DEM) using AES-CTR-HMAC. 27 | * 28 | *

During encryption, the KEM step takes the recipient's public key and produces a DEM key and an 29 | * ephemeral public key. The DEM key is then used to encrypt the credit card data, and the ephemeral 30 | * public key is sent as the ephemeralPublicKey field of the payload. 31 | * 32 | *

To decrypt, the recipient must use their private key to compute an ECDH shared secret from the 33 | * ephemeral public key, and from that derive the DEM key using HKDF. If the recipient keeps the 34 | * private key in a HSM, they cannot load the private key in Tink, but they can implement this 35 | * interface and configure Tink to use their custom KEM implementation with {@link 36 | * PaymentMethodTokenRecipient.Builder#addRecipientKem}. 37 | * 38 | * @see Google Payment 39 | * Method Token standard 40 | * @since 1.1.0 41 | */ 42 | public interface PaymentMethodTokenRecipientKem { 43 | /** 44 | * Computes a shared secret from the {@code ephemeralPublicKey}, using ECDH. 45 | * 46 | *

{@code ephemeralPublicKey} is a point on the elliptic curve defined in the Google Payment Method 48 | * Token standard, encoded in uncompressed point format. In version ECv1 and ECv2 of the 49 | * standard, the elliptic curve is NIST P-256. 50 | * 51 | *

Note that you only needs to compute the shared secret, but you don't have to derive the DEM 52 | * key with HKDF -- that process is handled by Tink. 53 | */ 54 | byte[] computeSharedSecret(final byte[] ephemeralPublicKey) throws GeneralSecurityException; 55 | } 56 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.subtle.Base64; 20 | import com.google.crypto.tink.subtle.EllipticCurves; 21 | import java.io.File; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.security.GeneralSecurityException; 25 | import java.security.KeyPair; 26 | import java.security.interfaces.ECPublicKey; 27 | 28 | /** 29 | * A util that generates key pairs for the recipient side of Google 31 | * Payment Method Token. 32 | * 33 | *

Usage

34 | * 35 | *
36 |  * bazel build apps/paymentmethodtoken/...
37 |  * ./bazel-bin/apps/paymentmethodtoken/recipientkeygen
38 |  * 
39 | * 40 | *

Running that command will generate a fresh key pair. The private/public key can be found in 41 | * private_key.bin/public_key.bin. The content of private_key.bin can be passed to {@link 42 | * PaymentMethodTokenRecipient.Builder#addRecipientPrivateKey} and the content of public_key.bin can 43 | * be passed to {@link PaymentMethodTokenSender.Builder#rawUncompressedRecipientPublicKey}. 44 | */ 45 | public final class PaymentMethodTokenRecipientKeyGen { 46 | private static final String PRIVATE_KEY_FILE = "private_key.bin"; 47 | 48 | private static final String PUBLIC_KEY_FILE = "public_key.bin"; 49 | 50 | private static void generateKey() throws GeneralSecurityException, IOException { 51 | KeyPair keyPair = EllipticCurves.generateKeyPair(PaymentMethodTokenConstants.P256_CURVE_TYPE); 52 | writeBase64(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded()); 53 | 54 | ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); 55 | writeBase64( 56 | PUBLIC_KEY_FILE, 57 | EllipticCurves.pointEncode( 58 | PaymentMethodTokenConstants.P256_CURVE_TYPE, 59 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT, 60 | publicKey.getW())); 61 | } 62 | 63 | private static void writeBase64(String pathname, byte[] content) throws IOException { 64 | File out = new File(pathname); 65 | if (out.exists()) { 66 | System.out.println("Please make sure that " + pathname + " does not exist."); 67 | System.exit(-1); 68 | } 69 | FileOutputStream stream = new FileOutputStream(out); 70 | stream.write(Base64.encode(content, Base64.DEFAULT | Base64.NO_WRAP)); 71 | stream.close(); 72 | } 73 | 74 | public static void main(String[] args) throws GeneralSecurityException, IOException { 75 | System.out.println("Generating key...."); 76 | generateKey(); 77 | System.out.println("done."); 78 | } 79 | 80 | private PaymentMethodTokenRecipientKeyGen() {} 81 | } 82 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.subtle.Base64; 20 | import com.google.crypto.tink.subtle.Bytes; 21 | import com.google.crypto.tink.subtle.EllipticCurves; 22 | import com.google.crypto.tink.subtle.EngineFactory; 23 | import java.nio.charset.StandardCharsets; 24 | import java.security.GeneralSecurityException; 25 | import java.security.interfaces.ECPrivateKey; 26 | import java.security.interfaces.ECPublicKey; 27 | import javax.crypto.Cipher; 28 | import javax.crypto.Mac; 29 | import javax.crypto.spec.IvParameterSpec; 30 | import javax.crypto.spec.SecretKeySpec; 31 | 32 | /** Various helpers. */ 33 | final class PaymentMethodTokenUtil { 34 | static ECPublicKey rawUncompressedEcPublicKey(String rawUncompressedPublicKey) 35 | throws GeneralSecurityException { 36 | return EllipticCurves.getEcPublicKey( 37 | PaymentMethodTokenConstants.P256_CURVE_TYPE, 38 | PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT, 39 | Base64.decode(rawUncompressedPublicKey)); 40 | } 41 | 42 | static ECPublicKey x509EcPublicKey(String x509PublicKey) throws GeneralSecurityException { 43 | return EllipticCurves.getEcPublicKey(Base64.decode(x509PublicKey)); 44 | } 45 | 46 | static ECPrivateKey pkcs8EcPrivateKey(String pkcs8PrivateKey) throws GeneralSecurityException { 47 | return EllipticCurves.getEcPrivateKey(Base64.decode(pkcs8PrivateKey)); 48 | } 49 | 50 | static byte[] toLengthValue(String... chunks) throws GeneralSecurityException { 51 | byte[] out = new byte[0]; 52 | for (String chunk : chunks) { 53 | byte[] bytes = chunk.getBytes(StandardCharsets.UTF_8); 54 | out = Bytes.concat(out, Bytes.intToByteArray(4, bytes.length)); 55 | out = Bytes.concat(out, bytes); 56 | } 57 | return out; 58 | } 59 | 60 | static byte[] aesCtr(final byte[] encryptionKey, final byte[] message) 61 | throws GeneralSecurityException { 62 | Cipher cipher = EngineFactory.CIPHER.getInstance(PaymentMethodTokenConstants.AES_CTR_ALGO); 63 | cipher.init( 64 | Cipher.ENCRYPT_MODE, 65 | new SecretKeySpec(encryptionKey, "AES"), 66 | new IvParameterSpec(PaymentMethodTokenConstants.AES_CTR_ZERO_IV)); 67 | return cipher.doFinal(message); 68 | } 69 | 70 | static byte[] hmacSha256(final byte[] macKey, final byte[] encryptedMessage) 71 | throws GeneralSecurityException { 72 | SecretKeySpec key = new SecretKeySpec(macKey, PaymentMethodTokenConstants.HMAC_SHA256_ALGO); 73 | Mac mac = EngineFactory.MAC.getInstance(PaymentMethodTokenConstants.HMAC_SHA256_ALGO); 74 | mac.init(key); 75 | return mac.doFinal(encryptedMessage); 76 | } 77 | 78 | private PaymentMethodTokenUtil() {} 79 | } 80 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import com.google.crypto.tink.PublicKeySign; 20 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 21 | import com.google.crypto.tink.subtle.Base64; 22 | import com.google.crypto.tink.subtle.EcdsaSignJce; 23 | import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding; 24 | import com.google.errorprone.annotations.CanIgnoreReturnValue; 25 | import com.google.gson.JsonArray; 26 | import com.google.gson.JsonObject; 27 | import com.google.gson.JsonParseException; 28 | import com.google.gson.internal.Streams; 29 | import com.google.gson.stream.JsonWriter; 30 | import java.io.IOException; 31 | import java.io.StringWriter; 32 | import java.security.GeneralSecurityException; 33 | import java.security.interfaces.ECPrivateKey; 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | /** 38 | * Creates a signed certificate with the intermediate signing keys used by the sender in certain 39 | * protocol versions. 40 | * 41 | * @since 1.1.0 42 | */ 43 | public class SenderIntermediateCertFactory { 44 | private final List signers; 45 | private final String intermediateSigningKey; 46 | private final String protocolVersion; 47 | private final String senderId; 48 | private final long expiration; 49 | 50 | private SenderIntermediateCertFactory( 51 | String protocolVersion, 52 | String senderId, 53 | List senderSigningKeys, 54 | String intermediateSigningKey, 55 | long expiration) 56 | throws GeneralSecurityException { 57 | if (!ProtocolVersionConfig.forProtocolVersion(protocolVersion) 58 | .supportsIntermediateSigningKeys) { 59 | throw new IllegalArgumentException("invalid version: " + protocolVersion); 60 | } 61 | if (senderSigningKeys.isEmpty()) { 62 | throw new IllegalArgumentException( 63 | "must add at least one sender's signing key using Builder.addSenderSigningKey"); 64 | } 65 | if (expiration == 0) { 66 | throw new IllegalArgumentException("must set expiration using Builder.expiration"); 67 | } 68 | if (expiration < 0) { 69 | throw new IllegalArgumentException("invalid negative expiration"); 70 | } 71 | this.protocolVersion = protocolVersion; 72 | this.senderId = senderId; 73 | this.signers = new ArrayList<>(); 74 | for (ECPrivateKey senderSigningKey : senderSigningKeys) { 75 | this.signers.add( 76 | new EcdsaSignJce( 77 | senderSigningKey, PaymentMethodTokenConstants.ECDSA_HASH_SHA256, EcdsaEncoding.DER)); 78 | } 79 | this.intermediateSigningKey = intermediateSigningKey; 80 | this.expiration = expiration; 81 | } 82 | 83 | /** 84 | * Builder for {@link SenderIntermediateCertFactory}. 85 | * 86 | * @since 1.1.0 87 | */ 88 | public static class Builder { 89 | private final List senderSigningKeys = new ArrayList<>(); 90 | private String intermediateSigningKey; 91 | private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2; 92 | private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID; 93 | private long expiration; 94 | 95 | public Builder() {} 96 | 97 | /** Sets the protocolVersion. */ 98 | @CanIgnoreReturnValue 99 | public Builder protocolVersion(String val) { 100 | protocolVersion = val; 101 | return this; 102 | } 103 | 104 | /** Sets the sender Id. */ 105 | @CanIgnoreReturnValue 106 | public Builder senderId(String val) { 107 | senderId = val; 108 | return this; 109 | } 110 | 111 | /** Sets the expiration in millis since epoch. */ 112 | @CanIgnoreReturnValue 113 | public Builder expiration(long millisSinceEpoch) { 114 | expiration = millisSinceEpoch; 115 | return this; 116 | } 117 | 118 | /** 119 | * Adds a signing key of the sender. 120 | * 121 | *

It must be base64 encoded PKCS8 private key. 122 | */ 123 | @CanIgnoreReturnValue 124 | public Builder addSenderSigningKey(String val) throws GeneralSecurityException { 125 | return addSenderSigningKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val)); 126 | } 127 | 128 | /** 129 | * Adds a signing key of the sender. 130 | * 131 | *

It must be base64 encoded PKCS8 private key. 132 | */ 133 | @CanIgnoreReturnValue 134 | public Builder addSenderSigningKey(ECPrivateKey val) throws GeneralSecurityException { 135 | this.senderSigningKeys.add(val); 136 | return this; 137 | } 138 | 139 | /** 140 | * Sets the intermediate signing key being signed. 141 | * 142 | *

The public key specified here is a base64 (no wrapping, padded) version of the key encoded 143 | * in ASN.1 type SubjectPublicKeyInfo defined in the X.509 standard. 144 | */ 145 | @CanIgnoreReturnValue 146 | public Builder senderIntermediateSigningKey(String val) throws GeneralSecurityException { 147 | // Parsing to validate the format 148 | Object unused = PaymentMethodTokenUtil.x509EcPublicKey(val); 149 | intermediateSigningKey = val; 150 | return this; 151 | } 152 | 153 | public SenderIntermediateCertFactory build() throws GeneralSecurityException { 154 | return new SenderIntermediateCertFactory( 155 | protocolVersion, senderId, senderSigningKeys, intermediateSigningKey, expiration); 156 | } 157 | } 158 | 159 | static String jsonEncodeSignedKey(String intermediateSigningKey, long expiration) { 160 | try { 161 | JsonObject jsonObj = new JsonObject(); 162 | jsonObj.addProperty(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY, intermediateSigningKey); 163 | jsonObj.addProperty( 164 | PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY, Long.toString(expiration)); 165 | StringWriter stringWriter = new StringWriter(); 166 | JsonWriter jsonWriter = new JsonWriter(stringWriter); 167 | jsonWriter.setHtmlSafe(true); 168 | Streams.write(jsonObj, jsonWriter); 169 | return stringWriter.toString(); 170 | } catch (JsonParseException | IllegalStateException | IOException e) { 171 | throw new AssertionError("Failed to perform JSON encoding", e); 172 | } 173 | } 174 | 175 | static String jsonEncodeCertificate(String signedKey, ArrayList signatures) { 176 | try { 177 | JsonArray jsonSignatures = new JsonArray(); 178 | for (String signature : signatures) { 179 | jsonSignatures.add(signature); 180 | } 181 | JsonObject result = new JsonObject(); 182 | result.addProperty(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY, signedKey); 183 | result.add(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY, jsonSignatures); 184 | StringWriter stringWriter = new StringWriter(); 185 | JsonWriter jsonWriter = new JsonWriter(stringWriter); 186 | jsonWriter.setHtmlSafe(true); 187 | Streams.write(result, jsonWriter); 188 | return stringWriter.toString(); 189 | } catch (JsonParseException | IllegalStateException | IOException e) { 190 | throw new AssertionError("Failed to perform JSON encoding", e); 191 | } 192 | } 193 | 194 | /** 195 | * Creates the certificate. 196 | * 197 | *

This will return a serialized JSONObject in the following format: 198 | * 199 | *

200 |    *   {
201 |    *     // {
202 |    *     //   // A string that identifies this cert
203 |    *     //   "keyValue": "ZXBoZW1lcmFsUHVibGljS2V5"
204 |    *     //   // string (UTC milliseconds since epoch)
205 |    *     //   "expiration": "1520836260646",
206 |    *     // }
207 |    *     "signedKey": "... serialized JSON shown in comment above ...",
208 |    *     "signatures": ["signature1", "signature2", ...],
209 |    *   }
210 |    * 
211 | */ 212 | public String create() throws GeneralSecurityException { 213 | String signedKey = jsonEncodeSignedKey(intermediateSigningKey, expiration); 214 | byte[] toSignBytes = 215 | PaymentMethodTokenUtil.toLengthValue( 216 | // The order of the parameters matters. 217 | senderId, protocolVersion, signedKey); 218 | ArrayList signatures = new ArrayList<>(); 219 | for (PublicKeySign signer : signers) { 220 | byte[] signature = signer.sign(toSignBytes); 221 | signatures.add(Base64.encode(signature)); 222 | } 223 | return jsonEncodeCertificate(signedKey, signatures); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:gen_java_test_rules.bzl", "gen_java_test_rules") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) 6 | 7 | # Tests 8 | 9 | java_library( 10 | name = "generator_test", 11 | testonly = 1, 12 | srcs = glob([ 13 | "**/*.java", 14 | ]), 15 | deps = [ 16 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:google_payments_public_keys_manager", 17 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:jwt_key_converter", 18 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:keys_downloader", 19 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_constants", 20 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_decrypt", 21 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_encrypt", 22 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient", 23 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_kem", 24 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_sender", 25 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_util", 26 | "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:sender_intermediate_cert_factory", 27 | "@maven//:com_google_code_gson_gson", 28 | "@maven//:com_google_crypto_tink_tink", 29 | "@maven//:com_google_errorprone_error_prone_annotations", 30 | "@maven//:com_google_http_client_google_http_client", 31 | "@maven//:com_google_truth_truth", 32 | "@maven//:junit_junit", 33 | ], 34 | ) 35 | 36 | gen_java_test_rules( 37 | test_files = glob( 38 | ["**/*Test.java"], 39 | ), 40 | deps = [ 41 | ":generator_test", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/ExampleTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | /** 26 | * An example on how to encrypt and decrypt messages using the paymentmethodtoken library. 27 | * 28 | *

https://developers.google.com/pay/api/web/guides/resources/payment-data-cryptography 29 | */ 30 | @RunWith(JUnit4.class) 31 | public final class ExampleTest { 32 | 33 | /** 34 | * Sample Google private signing key for the ECv2 protocolVersion. 35 | * 36 | *

Corresponds to one of the public keys in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}. 37 | */ 38 | private static final String GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64 = 39 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev" 40 | + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz" 41 | + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL"; 42 | 43 | /** Sample Google provided JSON with its public signing keys. */ 44 | private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON = 45 | "{\n" 46 | + " \"keys\": [\n" 47 | + " {\n" 48 | + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQtlxizmLFynw" 49 | + "HcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\",\n" 50 | + " \"protocolVersion\": \"ECv1\"\n" 51 | + " },\n" 52 | + " {\n" 53 | + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM" 54 | + "43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==\",\n" 55 | + " \"keyExpiration\": \"" 56 | + (System.currentTimeMillis() + 24 * 60 * 60 * 1000) 57 | + "\",\n" 58 | + " \"protocolVersion\": \"ECv2\"\n" 59 | + " },\n" 60 | + " {\n" 61 | + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENXvYqxD5WayKYhuXQevdGdLA8i" 62 | + "fV4LsRS2uKvFo8wwyiwgQHB9DiKzG6T/P1Fu9Bl7zWy/se5Dy4wk1mJoPuxg==\",\n" 63 | + " \"keyExpiration\": \"" 64 | + (System.currentTimeMillis() + 24 * 60 * 60 * 1000) 65 | + "\",\n" 66 | + " \"protocolVersion\": \"ECv2SigningOnly\"\n" 67 | + " }\n" 68 | + " ]\n" 69 | + "}"; 70 | 71 | /** 72 | * Sample Google intermediate private signing key. 73 | * 74 | *

Corresponds to {@link #GOOGLE_SIGNING_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}. 75 | */ 76 | private static final String GOOGLE_SIGNING_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 = 77 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev" 78 | + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz" 79 | + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL"; 80 | 81 | /** 82 | * Sample Google intermediate public signing key. 83 | * 84 | *

Corresponds to {@link #GOOGLE_SIGNING_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64}. 85 | */ 86 | private static final String GOOGLE_SIGNING_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 = 87 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR" 88 | + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw=="; 89 | 90 | /** 91 | * Sample merchant private key. 92 | * 93 | *

Corresponds to {@link #MERCHANT_PUBLIC_KEY_BASE64} 94 | * 95 | *

 96 |    * openssl pkcs8 -topk8 -inform PEM -outform PEM -in merchant-key.pem -nocrypt
 97 |    * 
98 | */ 99 | private static final String MERCHANT_PRIVATE_KEY_PKCS8_BASE64 = 100 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj" 101 | + "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx" 102 | + "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm"; 103 | 104 | /** 105 | * Sample merchant public key. 106 | * 107 | *

Corresponds to {@link #MERCHANT_PRIVATE_KEY_PKCS8_BASE64} 108 | * 109 | *

Created with: 110 | * 111 | *

112 |    * openssl ec -in merchant-key.pem -pubout -text -noout 2> /dev/null | grep "pub:" -A5 \
113 |    *     | xxd -r -p | base64
114 |    * 
115 | */ 116 | private static final String MERCHANT_PUBLIC_KEY_BASE64 = 117 | "BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y="; 118 | 119 | @Test 120 | public void testSealAndUnseal() throws Exception { 121 | // Create a PaymentMethodTokenSender to send sealed messages to a merchant. 122 | PaymentMethodTokenSender sender = 123 | new PaymentMethodTokenSender.Builder() 124 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2) 125 | .senderIntermediateSigningKey(GOOGLE_SIGNING_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64) 126 | .senderIntermediateCert( 127 | new SenderIntermediateCertFactory.Builder() 128 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2) 129 | .addSenderSigningKey(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64) 130 | .senderIntermediateSigningKey( 131 | GOOGLE_SIGNING_INTERMEDIATE_PUBLIC_KEY_X509_BASE64) 132 | .expiration(System.currentTimeMillis() + 24 * 60 * 60 * 1000) 133 | .build() 134 | .create()) 135 | .recipientId("[YOUR MERCHANT ID]") 136 | .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64) 137 | .build(); 138 | 139 | String encryptedMessage = sender.seal("plaintext"); 140 | 141 | // Create a PaymentMethodTokenRecipient to receive sealed messages. 142 | PaymentMethodTokenRecipient recipient = 143 | new PaymentMethodTokenRecipient.Builder() 144 | .protocolVersion("ECv2") 145 | // IMPORTANT: Instead of using "senderVerifyingKeys" to set the verifying public keys 146 | // of the sender, prefer calling: 147 | // .fetchSenderVerifyingKeysWith(GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION) 148 | .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) 149 | .recipientId("[YOUR MERCHANT ID]") 150 | .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64) 151 | .build(); 152 | 153 | String decryptedMessage = recipient.unseal(encryptedMessage); 154 | 155 | assertThat(decryptedMessage).isEqualTo("plaintext"); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static org.junit.Assert.assertNotSame; 20 | import static org.junit.Assert.assertSame; 21 | import static org.junit.Assert.fail; 22 | 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | /** Tests for {@link GooglePaymentsPublicKeysManager}. */ 28 | @RunWith(JUnit4.class) 29 | public class GooglePaymentsPublicKeyManagerTest { 30 | @Test 31 | public void builderShouldReturnSingletonsWhenMatching() { 32 | assertSame( 33 | GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION, 34 | new GooglePaymentsPublicKeysManager.Builder().build()); 35 | assertSame( 36 | GooglePaymentsPublicKeysManager.INSTANCE_TEST, 37 | new GooglePaymentsPublicKeysManager.Builder() 38 | .setKeysUrl(GooglePaymentsPublicKeysManager.KEYS_URL_TEST) 39 | .build()); 40 | } 41 | 42 | @Test 43 | public void builderShouldReturnDifferentInstanceWhenNotMatchingSingletons() { 44 | assertNotSame( 45 | GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION, 46 | new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build()); 47 | assertNotSame( 48 | GooglePaymentsPublicKeysManager.INSTANCE_TEST, 49 | new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build()); 50 | } 51 | 52 | @Test 53 | public void builderShouldThrowIllegalArgumentExceptionWhenUrlIsNotHttps() { 54 | try { 55 | new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("http://abc").build(); 56 | fail("Expected IllegalArgumentException"); 57 | } catch (IllegalArgumentException ex) { 58 | // expected. 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static java.nio.charset.StandardCharsets.UTF_8; 20 | import static org.junit.Assert.assertEquals; 21 | 22 | import java.util.ArrayList; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | /** Tests for the exact Json-Encoding produced. 28 | * 29 | * These tests test implementation details. Do not depend on the this. For example, the particular 30 | * ordering of the elements or the particular character escaping used may change in the future. 31 | * */ 32 | @RunWith(JUnit4.class) 33 | public final class PaymentMethodJsonEncodingTest { 34 | 35 | @Test 36 | public void testExactOutputOfJsonEncodeCiphertext() throws Exception { 37 | byte[] ciphertext = "CiPhErTeXt".getBytes(UTF_8); 38 | byte[] tag = "taaag".getBytes(UTF_8); 39 | byte[] ephemeralPublicKey = "ephemeral Public Key".getBytes(UTF_8); 40 | 41 | String jsonEncodedCiphertext = 42 | PaymentMethodTokenHybridEncrypt.jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey); 43 | 44 | // JSONObject uses a HashMap, where the ordering is not defined. The ordering is however 45 | // deterministic. And for jsonEncodeCiphertext, the order happens to be first "encryptedMessage" 46 | // then "ephemeralPublicKey", and finally "tag". Also, JSONObject uses HTML-safe encoding. 47 | assertEquals( 48 | "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":" 49 | + "\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}", 50 | jsonEncodedCiphertext); 51 | } 52 | 53 | @Test 54 | public void testExactOutputOfJsonEncodeSignedMessage() throws Exception { 55 | String senderIntermediateCert = 56 | "{\"signedKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\"" 57 | + ":\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]}"; 58 | String version = "ECv1"; 59 | String message = 60 | "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":\"ZXBoZW1l" 61 | + "cmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}"; 62 | byte[] signature = "the signature".getBytes(UTF_8); 63 | 64 | String jsonEncodedSignedMessage = 65 | PaymentMethodTokenSender.jsonEncodeSignedMessage( 66 | message, version, signature, senderIntermediateCert); 67 | 68 | String expected = 69 | "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"intermediateSigningKey\":{\"signe" 70 | + "dKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":" 71 | + "\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]},\"protocolVersion\"" 72 | + ":\"ECv1\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u00" 73 | + "3d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u00" 74 | + "3d\\\",\\\"tag\\\":\\\"dGFhYWc\\\\u003d\\\"}\"}"; 75 | assertEquals(expected, jsonEncodedSignedMessage); 76 | 77 | String expected2 = 78 | "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"protocolVersion\":\"ECv1\",\"sign" 79 | + "edMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u003d\\\\u003d\\\"," 80 | + "\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u003d\\\",\\\"tag\\" 81 | + "\":\\\"dGFhYWc\\\\u003d\\\"}\"}"; 82 | 83 | String jsonEncodedSignedMessage2 = 84 | PaymentMethodTokenSender.jsonEncodeSignedMessage(message, version, signature, null); 85 | assertEquals(expected2, jsonEncodedSignedMessage2); 86 | } 87 | 88 | @Test 89 | public void testExactOutputOfJsonEncodedSignedKey() throws Exception { 90 | String intermediateSigningKey = 91 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA" 92 | + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw=="; 93 | long expiration = 1520836260646L; 94 | assertEquals( 95 | "{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1" 96 | + "TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\",\"keyExpiration\"" 97 | + ":\"1520836260646\"}", 98 | SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration)); 99 | } 100 | 101 | @Test 102 | public void testExactOutputOfJsonEncodeCertificate() throws Exception { 103 | String intermediateSigningKey = 104 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA" 105 | + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw=="; 106 | long expiration = 1520836260646L; 107 | String signedKey = 108 | SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration); 109 | ArrayList signatures = new ArrayList<>(); 110 | signatures.add("iTzFvzFRxyCw=="); 111 | signatures.add("abcde090/+=="); 112 | signatures.add("xyz"); 113 | String expected = 114 | "{\"signedKey\":\"{\\\"keyValue\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE" 115 | + "/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRx" 116 | + "yCw\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":\\\"1520836260646\\\"}\",\"signatur" 117 | + "es\":[\"iTzFvzFRxyCw\\u003d\\u003d\",\"abcde090/+\\u003d\\u003d\",\"xyz\"]}"; 118 | assertEquals( 119 | expected, SenderIntermediateCertFactory.jsonEncodeCertificate(signedKey, signatures)); 120 | } 121 | 122 | @Test 123 | public void testExactOutputOfWeirdJsonEncodeCertificate() throws Exception { 124 | String intermediateSigningKey = 125 | "\"\\=="; 126 | long expiration = -123; 127 | String signedKey = 128 | SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration); 129 | ArrayList signatures = new ArrayList<>(); 130 | signatures.add(""); 131 | signatures.add("\\\"/+=="); 132 | String expected = 133 | "{\"signedKey\":\"{\\\"keyValue\\\":\\\"\\\\\\\"\\\\\\\\\\\\u003d\\\\u003d" 134 | + "\\\",\\\"keyExpiration\\\":\\\"-123\\\"}\",\"signatures\":[\"\",\"\\\\\\\"/+\\u003d" 135 | + "\\u003d\"]}"; 136 | assertEquals( 137 | expected, SenderIntermediateCertFactory.jsonEncodeCertificate(signedKey, signatures)); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static java.nio.charset.StandardCharsets.UTF_8; 20 | import static org.junit.Assert.assertArrayEquals; 21 | import static org.junit.Assert.fail; 22 | 23 | import com.google.crypto.tink.HybridDecrypt; 24 | import com.google.crypto.tink.HybridEncrypt; 25 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 26 | import com.google.crypto.tink.subtle.Base64; 27 | import com.google.crypto.tink.subtle.EllipticCurves; 28 | import com.google.crypto.tink.subtle.Random; 29 | import com.google.gson.JsonObject; 30 | import com.google.gson.JsonParser; 31 | import java.security.GeneralSecurityException; 32 | import java.security.KeyPair; 33 | import java.security.KeyPairGenerator; 34 | import java.security.interfaces.ECPrivateKey; 35 | import java.security.interfaces.ECPublicKey; 36 | import java.security.spec.ECParameterSpec; 37 | import java.util.Arrays; 38 | import org.junit.Test; 39 | import org.junit.runner.RunWith; 40 | import org.junit.runners.JUnit4; 41 | 42 | /** Unit tests for {@code PaymentMethodTokenHybridDecrypt}. */ 43 | @RunWith(JUnit4.class) 44 | public class PaymentMethodTokenHybridDecryptTest { 45 | @Test 46 | public void testModifyDecrypt() throws Exception { 47 | ECParameterSpec spec = EllipticCurves.getNistP256Params(); 48 | KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); 49 | keyGen.initialize(spec); 50 | KeyPair recipientKey = keyGen.generateKeyPair(); 51 | ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic(); 52 | ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate(); 53 | 54 | HybridEncrypt hybridEncrypt = 55 | new PaymentMethodTokenHybridEncrypt(recipientPublicKey, ProtocolVersionConfig.EC_V1); 56 | HybridDecrypt hybridDecrypt = 57 | new PaymentMethodTokenHybridDecrypt(recipientPrivateKey, ProtocolVersionConfig.EC_V1); 58 | testModifyDecrypt(hybridEncrypt, hybridDecrypt); 59 | } 60 | 61 | public void testModifyDecrypt(HybridEncrypt hybridEncrypt, HybridDecrypt hybridDecrypt) 62 | throws Exception { 63 | byte[] plaintext = Random.randBytes(111); 64 | byte[] context = "context info".getBytes(UTF_8); 65 | 66 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context); 67 | byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context); 68 | assertArrayEquals(plaintext, decrypted); 69 | 70 | JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject(); 71 | 72 | // Modify public key. 73 | byte[] kem = 74 | Base64.decode( 75 | json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString()); 76 | for (int bytes = 0; bytes < kem.length; bytes++) { 77 | for (int bit = 0; bit < 8; bit++) { 78 | byte[] modifiedPublicKey = Arrays.copyOf(kem, kem.length); 79 | modifiedPublicKey[bytes] ^= (byte) (1 << bit); 80 | json.addProperty( 81 | PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY, 82 | Base64.encode(modifiedPublicKey)); 83 | try { 84 | hybridDecrypt.decrypt(json.toString().getBytes(UTF_8), context); 85 | fail("Invalid ciphertext, should have thrown exception"); 86 | } catch (GeneralSecurityException expected) { 87 | // Expected 88 | } 89 | } 90 | } 91 | 92 | // Modify payload. 93 | byte[] payload = 94 | Base64.decode( 95 | json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString()); 96 | for (int bytes = 0; bytes < payload.length; bytes++) { 97 | for (int bit = 0; bit < 8; bit++) { 98 | byte[] modifiedPayload = Arrays.copyOf(payload, payload.length); 99 | modifiedPayload[bytes] ^= (byte) (1 << bit); 100 | json.addProperty( 101 | PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(modifiedPayload)); 102 | try { 103 | hybridDecrypt.decrypt(json.toString().getBytes(UTF_8), context); 104 | fail("Invalid ciphertext, should have thrown exception"); 105 | } catch (GeneralSecurityException expected) { 106 | // Expected 107 | } 108 | } 109 | } 110 | 111 | // Modify context. 112 | try { 113 | hybridDecrypt.decrypt(ciphertext, Arrays.copyOf(context, context.length - 1)); 114 | fail("Invalid context, should have thrown exception"); 115 | } catch (GeneralSecurityException expected) { 116 | // Expected 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static org.junit.Assert.assertArrayEquals; 20 | import static org.junit.Assert.assertEquals; 21 | 22 | import com.google.crypto.tink.HybridDecrypt; 23 | import com.google.crypto.tink.HybridEncrypt; 24 | import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig; 25 | import com.google.crypto.tink.subtle.EllipticCurves; 26 | import com.google.crypto.tink.subtle.Random; 27 | import java.nio.charset.StandardCharsets; 28 | import java.security.GeneralSecurityException; 29 | import java.security.KeyPair; 30 | import java.security.KeyPairGenerator; 31 | import java.security.interfaces.ECPrivateKey; 32 | import java.security.interfaces.ECPublicKey; 33 | import java.security.spec.ECParameterSpec; 34 | import java.util.Set; 35 | import java.util.TreeSet; 36 | import org.junit.Test; 37 | import org.junit.runner.RunWith; 38 | import org.junit.runners.JUnit4; 39 | 40 | /** Unit tests for {@code PaymentMethodTokenHybridEncrypt}. */ 41 | @RunWith(JUnit4.class) 42 | public class PaymentMethodTokenHybridEncryptTest { 43 | @Test 44 | public void testBasicMultipleEncrypts() throws Exception { 45 | ECParameterSpec spec = EllipticCurves.getNistP256Params(); 46 | KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); 47 | keyGen.initialize(spec); 48 | KeyPair recipientKey = keyGen.generateKeyPair(); 49 | ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic(); 50 | ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate(); 51 | 52 | HybridEncrypt hybridEncrypt = 53 | new PaymentMethodTokenHybridEncrypt(recipientPublicKey, ProtocolVersionConfig.EC_V1); 54 | HybridDecrypt hybridDecrypt = 55 | new PaymentMethodTokenHybridDecrypt(recipientPrivateKey, ProtocolVersionConfig.EC_V1); 56 | testBasicMultipleEncrypts(hybridEncrypt, hybridDecrypt); 57 | } 58 | 59 | public void testBasicMultipleEncrypts(HybridEncrypt hybridEncrypt, HybridDecrypt hybridDecrypt) 60 | throws Exception { 61 | byte[] plaintext = Random.randBytes(111); 62 | byte[] context = "context info".getBytes(StandardCharsets.UTF_8); 63 | // Makes sure that the encryption is randomized. 64 | Set ciphertexts = new TreeSet(); 65 | for (int j = 0; j < 100; j++) { 66 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context); 67 | if (ciphertexts.contains(new String(ciphertext, StandardCharsets.UTF_8))) { 68 | throw new GeneralSecurityException("Encryption is not randomized"); 69 | } 70 | ciphertexts.add(new String(ciphertext, StandardCharsets.UTF_8)); 71 | byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context); 72 | assertArrayEquals(plaintext, decrypted); 73 | } 74 | assertEquals(100, ciphertexts.size()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.paymentmethodtoken; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | import static org.junit.Assert.fail; 23 | 24 | import com.google.gson.JsonObject; 25 | import com.google.gson.JsonParser; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.JUnit4; 29 | 30 | /** Tests for {@link SenderIntermediateCertFactory}. */ 31 | @RunWith(JUnit4.class) 32 | public class SenderIntermediateCertFactoryTest { 33 | 34 | /** 35 | * Sample Google private signing key for the ECv2 protocolVersion. 36 | * 37 | *

Base64 version of the private key encoded in PKCS8 format. 38 | */ 39 | private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 = 40 | "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev" 41 | + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz" 42 | + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL"; 43 | 44 | /** 45 | * Sample Google intermediate public signing key for the ECv2 protocolVersion. 46 | * 47 | *

Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the 48 | * X.509 standard. 49 | */ 50 | private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 = 51 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR" 52 | + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw=="; 53 | 54 | @Test 55 | public void shouldProduceSenderIntermediateCertJson() throws Exception { 56 | String encoded = 57 | new SenderIntermediateCertFactory.Builder() 58 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2) 59 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64) 60 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64) 61 | .expiration(123456) 62 | .build() 63 | .create(); 64 | 65 | JsonObject decodedSignedIntermediateSigningKey = 66 | JsonParser.parseString(encoded).getAsJsonObject(); 67 | assertTrue(decodedSignedIntermediateSigningKey.get("signedKey").isJsonPrimitive()); 68 | assertTrue(decodedSignedIntermediateSigningKey.get("signatures").isJsonArray()); 69 | assertEquals(2, decodedSignedIntermediateSigningKey.size()); 70 | 71 | JsonObject signedKey = 72 | JsonParser.parseString(decodedSignedIntermediateSigningKey.get("signedKey").getAsString()) 73 | .getAsJsonObject(); 74 | assertTrue(signedKey.get("keyValue").getAsJsonPrimitive().isString()); 75 | assertTrue(signedKey.get("keyExpiration").getAsJsonPrimitive().isString()); 76 | assertEquals(2, signedKey.size()); 77 | assertEquals( 78 | GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64, 79 | signedKey.get("keyValue").getAsString()); 80 | assertEquals("123456", signedKey.get("keyExpiration").getAsString()); 81 | assertEquals(1, decodedSignedIntermediateSigningKey.get("signatures").getAsJsonArray().size()); 82 | assertFalse( 83 | decodedSignedIntermediateSigningKey 84 | .get("signatures") 85 | .getAsJsonArray() 86 | .get(0) 87 | .getAsString() 88 | .isEmpty()); 89 | } 90 | 91 | @Test 92 | public void shouldThrowIfExpirationNotSet() throws Exception { 93 | try { 94 | new SenderIntermediateCertFactory.Builder() 95 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2) 96 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64) 97 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64) 98 | // no expiration 99 | .build(); 100 | fail("Should have thrown!"); 101 | } catch (IllegalArgumentException expected) { 102 | assertEquals("must set expiration using Builder.expiration", expected.getMessage()); 103 | } 104 | } 105 | 106 | @Test 107 | public void shouldThrowIfExpirationIsNegative() throws Exception { 108 | try { 109 | new SenderIntermediateCertFactory.Builder() 110 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2) 111 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64) 112 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64) 113 | .expiration(-1) 114 | .build(); 115 | fail("Should have thrown!"); 116 | } catch (IllegalArgumentException expected) { 117 | assertEquals("invalid negative expiration", expected.getMessage()); 118 | } 119 | } 120 | 121 | @Test 122 | public void shouldThrowIfNoSenderSigningKeyAdded() throws Exception { 123 | try { 124 | new SenderIntermediateCertFactory.Builder() 125 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2) 126 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64) 127 | // no call to addSenderSigningKey 128 | .expiration(System.currentTimeMillis() + 24 * 60 * 60 * 1000) 129 | .build(); 130 | fail("Should have thrown!"); 131 | } catch (IllegalArgumentException expected) { 132 | assertEquals( 133 | "must add at least one sender's signing key using Builder.addSenderSigningKey", 134 | expected.getMessage()); 135 | } 136 | } 137 | 138 | @Test 139 | public void shouldSupportECV2SigningOnly() throws Exception { 140 | Object unused = new SenderIntermediateCertFactory.Builder() 141 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY) 142 | .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64) 143 | .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64) 144 | .expiration(123456) 145 | .build(); 146 | } 147 | 148 | @Test 149 | public void shouldThrowIfInvalidProtocolVersionSet() throws Exception { 150 | try { 151 | Object unused = new SenderIntermediateCertFactory.Builder() 152 | .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1) 153 | .build(); 154 | fail("Should have thrown!"); 155 | } catch (IllegalArgumentException expected) { 156 | assertEquals("invalid version: ECv1", expected.getMessage()); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /rewardedads/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) 6 | 7 | gen_maven_jar_rules( 8 | name = "maven", 9 | doctitle = "Tink Cryptography API for Google Mobile Rewarded Video Ads SSV", 10 | manifest_lines = [ 11 | "Automatic-Module-Name: com.google.crypto.tink.apps.rewardedads", 12 | ], 13 | root_packages = ["com.google.crypto.tink.apps.rewardedads"], 14 | deps = [ 15 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:keys_downloader", 16 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | licenses(["notice"]) 4 | 5 | java_library( 6 | name = "rewarded_ads_verifier", 7 | srcs = ["RewardedAdsVerifier.java"], 8 | deps = [ 9 | ":keys_downloader", 10 | "@maven//:com_google_code_gson_gson", 11 | "@maven//:com_google_crypto_tink_tink", 12 | "@maven//:com_google_errorprone_error_prone_annotations", 13 | "@maven//:com_google_http_client_google_http_client", 14 | ], 15 | ) 16 | 17 | java_library( 18 | name = "keys_downloader", 19 | srcs = ["KeysDownloader.java"], 20 | deps = [ 21 | "@maven//:com_google_code_findbugs_jsr305", 22 | "@maven//:com_google_errorprone_error_prone_annotations", 23 | "@maven//:com_google_http_client_google_http_client", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /rewardedads/src/test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:gen_java_test_rules.bzl", "gen_java_test_rules") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) 6 | 7 | # Tests 8 | 9 | java_library( 10 | name = "generator_test", 11 | testonly = 1, 12 | srcs = glob([ 13 | "**/*.java", 14 | ]), 15 | deps = [ 16 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:keys_downloader", 17 | "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier", 18 | "@maven//:com_google_code_gson_gson", 19 | "@maven//:com_google_crypto_tink_tink", 20 | "@maven//:com_google_errorprone_error_prone_annotations", 21 | "@maven//:com_google_http_client_google_http_client", 22 | "@maven//:junit_junit", 23 | ], 24 | ) 25 | 26 | gen_java_test_rules( 27 | test_files = glob([ 28 | "**/*Test.java", 29 | ]), 30 | deps = [ 31 | ":generator_test", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /tools/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | licenses(["notice"]) 4 | 5 | java_binary( 6 | name = "jarjar", 7 | main_class = "org.pantsbuild.jarjar.Main", 8 | visibility = ["//visibility:public"], 9 | runtime_deps = [ 10 | "@maven//:org_ow2_asm_asm", 11 | "@maven//:org_ow2_asm_asm_commons", 12 | "@maven//:org_pantsbuild_jarjar", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /tools/gen_java_test_rules.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | """Generate Java test rules from given test_files. 17 | 18 | Instead of having to create one test rule per test in the BUILD file, this rule 19 | provides a handy way to create a bunch of test rules for the specified test 20 | files. 21 | 22 | """ 23 | 24 | load("@rules_java//java:defs.bzl", "java_test") 25 | 26 | def gen_java_test_rules( 27 | test_files, 28 | deps, 29 | data = [], 30 | exclude_tests = [], 31 | default_test_size = "small", 32 | small_tests = [], 33 | medium_tests = [], 34 | large_tests = [], 35 | enormous_tests = [], 36 | flaky_tests = [], 37 | manual_tests = [], 38 | notsan_tests = [], 39 | no_rbe_tests = [], 40 | resources = [], 41 | tags = [], 42 | prefix = "", 43 | jvm_flags = [], 44 | args = [], 45 | visibility = None, 46 | shard_count = 1): 47 | for test in _get_test_names(test_files): 48 | if test in exclude_tests: 49 | continue 50 | test_size = default_test_size 51 | if test in small_tests: 52 | test_size = "small" 53 | if test in medium_tests: 54 | test_size = "medium" 55 | if test in large_tests: 56 | test_size = "large" 57 | if test in enormous_tests: 58 | test_size = "enormous" 59 | manual = [] 60 | if test in manual_tests: 61 | manual = ["manual"] 62 | notsan = [] 63 | if test in notsan_tests: 64 | notsan = ["notsan"] 65 | no_rbe = [] 66 | if test in no_rbe_tests: 67 | no_rbe = ["no_rbe"] 68 | flaky = 0 69 | if (test in flaky_tests) or ("flaky" in tags): 70 | flaky = 1 71 | java_class = _package_from_path( 72 | native.package_name() + "/" + _strip_right(test, ".java"), 73 | ) 74 | java_test( 75 | name = prefix + test, 76 | runtime_deps = deps, 77 | data = data, 78 | resources = resources, 79 | size = test_size, 80 | jvm_flags = jvm_flags, 81 | args = args, 82 | flaky = flaky, 83 | tags = tags + manual + notsan + no_rbe, 84 | test_class = java_class, 85 | visibility = visibility, 86 | shard_count = shard_count, 87 | ) 88 | 89 | def _get_test_names(test_files): 90 | test_names = [] 91 | for test_file in test_files: 92 | if not test_file.endswith("Test.java"): 93 | continue 94 | test_names += [test_file[:-5]] 95 | return test_names 96 | 97 | def _package_from_path(package_path, src_impls = None): 98 | src_impls = src_impls or ["src/test/java/", "javatests/", "java/"] 99 | for src_impl in src_impls: 100 | if not src_impl.endswith("/"): 101 | src_impl += "/" 102 | index = _index_of_end(package_path, src_impl) 103 | if index >= 0: 104 | package_path = package_path[index:] 105 | break 106 | return package_path.replace("/", ".") 107 | 108 | def _strip_right(s, suffix): 109 | if s.endswith(suffix): 110 | return s[0:len(s) - len(suffix)] 111 | else: 112 | return s 113 | 114 | def _index_of_end(s, part): 115 | index = s.find(part) 116 | if index >= 0: 117 | return index + len(part) 118 | return -1 119 | -------------------------------------------------------------------------------- /tools/gen_maven_jar_rules.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ Definition of gen_maven_jar_rules. """ 16 | 17 | load("//tools:jar_jar.bzl", "jar_jar") 18 | load("//tools:java_single_jar.bzl", "java_single_jar") 19 | load("//tools:javadoc.bzl", "javadoc_library") 20 | 21 | _EXTERNAL_JAVADOC_LINKS = [ 22 | "https://docs.oracle.com/javase/8/docs/api/", 23 | "https://developer.android.com/reference/", 24 | ] 25 | 26 | _TINK_PACKAGES = [ 27 | "com.google.crypto.tink", 28 | ] 29 | 30 | def gen_maven_jar_rules( 31 | name, 32 | deps = [], 33 | resources = [], 34 | root_packages = _TINK_PACKAGES, 35 | shaded_packages = [], 36 | shading_rules = "", 37 | exclude_packages = [], 38 | doctitle = "", 39 | android_api_level = 23, 40 | bottom_text = "", 41 | external_javadoc_links = _EXTERNAL_JAVADOC_LINKS, 42 | manifest_lines = []): 43 | """ 44 | Generates rules that generate Maven jars for a given package. 45 | 46 | Args: 47 | name: Given a name, this function generates 3 rules: a compiled package 48 | name.jar, a source package name-src.jar and a Javadoc package 49 | name-javadoc.jar. 50 | deps: A combination of the deps of java_single_jar and javadoc_library 51 | resources: A list of resource files. Files must be stored in 52 | src/main/resources. Mapping rules: src/main/resources/a/b/c.txt will be 53 | copied to a/b/c.txt in the output jar. 54 | root_packages: See javadoc_library 55 | shaded_packages: These packages will be shaded, according to the rules 56 | specified in shading_rules. 57 | shading_rules: The shading rules, must specified when shaded_packages is present. 58 | Rules file format can be found at https://github.com/bazelbuild/bazel/blob/master/third_party/jarjar/java/com/tonicsystems/jarjar/help.txt. 59 | exclude_packages: See javadoc_library 60 | doctitle: See javadoc_library 61 | android_api_level: See javadoc_library 62 | bottom_text: See javadoc_library 63 | external_javadoc_links: See javadoc_library 64 | manifest_lines: lines to put in the output manifest file (manifest 65 | files in the input jars are ignored) 66 | """ 67 | 68 | if shaded_packages: 69 | unshaded_jar = name + "-unshaded" 70 | java_single_jar( 71 | name = unshaded_jar, 72 | deps = deps, 73 | resources = resources, 74 | root_packages = root_packages + shaded_packages, 75 | manifest_lines = manifest_lines, 76 | ) 77 | jar_jar( 78 | name = name, 79 | input_jar = unshaded_jar, 80 | rules = shading_rules, 81 | ) 82 | else: 83 | java_single_jar( 84 | name = name, 85 | deps = deps, 86 | resources = resources, 87 | root_packages = root_packages, 88 | manifest_lines = manifest_lines, 89 | ) 90 | 91 | source_jar_name = name + "-src" 92 | java_single_jar( 93 | name = source_jar_name, 94 | deps = deps, 95 | root_packages = root_packages, 96 | source_jar = True, 97 | ) 98 | 99 | javadoc_name = name + "-javadoc" 100 | javadoc_library( 101 | name = javadoc_name, 102 | deps = deps, 103 | root_packages = root_packages, 104 | srcs = [":%s" % source_jar_name], 105 | doctitle = doctitle, 106 | exclude_packages = exclude_packages, 107 | android_api_level = android_api_level, 108 | bottom_text = bottom_text, 109 | external_javadoc_links = external_javadoc_links, 110 | ) 111 | -------------------------------------------------------------------------------- /tools/jar_jar.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """starlark rules for jarjar. See https://github.com/pantsbuild/jarjar 15 | """ 16 | 17 | load("@rules_java//java:defs.bzl", "JavaInfo") 18 | 19 | def _jar_jar_impl(ctx): 20 | ctx.actions.run( 21 | inputs = [ctx.file.rules, ctx.file.input_jar], 22 | outputs = [ctx.outputs.jar], 23 | executable = ctx.executable._jarjar, 24 | progress_message = "jarjar %s" % ctx.label, 25 | arguments = ["process", ctx.file.rules.path, ctx.file.input_jar.path, ctx.outputs.jar.path], 26 | ) 27 | 28 | return [ 29 | JavaInfo( 30 | output_jar = ctx.outputs.jar, 31 | compile_jar = ctx.outputs.jar, 32 | ), 33 | DefaultInfo(files = depset([ctx.outputs.jar])), 34 | ] 35 | 36 | jar_jar = rule( 37 | implementation = _jar_jar_impl, 38 | attrs = { 39 | "input_jar": attr.label(allow_single_file = True), 40 | "rules": attr.label(allow_single_file = True), 41 | "_jarjar": attr.label(executable = True, cfg = "exec", default = Label("//tools:jarjar")), 42 | }, 43 | outputs = { 44 | "jar": "%{name}.jar", 45 | }, 46 | provides = [JavaInfo], 47 | ) 48 | -------------------------------------------------------------------------------- /tools/java_single_jar.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ Definition of java_single_jar. """ 16 | 17 | load("@rules_java//java:defs.bzl", "JavaInfo") 18 | 19 | def _check_non_empty(value, name): 20 | if not value: 21 | fail("%s must be non-empty" % name) 22 | 23 | def _java_single_jar(ctx): 24 | _check_non_empty(ctx.attr.root_packages, "root_packages") 25 | 26 | inputs = depset() 27 | if ctx.attr.source_jar: 28 | inputs = depset(transitive = [dep[JavaInfo].transitive_source_jars for dep in ctx.attr.deps]) 29 | else: 30 | inputs = depset(transitive = [dep[JavaInfo].transitive_runtime_jars for dep in ctx.attr.deps]) 31 | 32 | args = ctx.actions.args() 33 | args.add_all("--sources", inputs) 34 | args.use_param_file( 35 | "@%s", 36 | use_always = True, 37 | ) 38 | args.set_param_file_format("multiline") 39 | args.add("--output", ctx.outputs.jar) 40 | args.add("--normalize") 41 | 42 | resource_files = depset( 43 | transitive = [resource.files for resource in ctx.attr.resources], 44 | ).to_list() 45 | args.add("--resources") 46 | for resource_file in resource_files: 47 | if not resource_file.path.startswith("src/main/resources"): 48 | fail("resource %s must be stored in src/main/resources/" % resource_file.path) 49 | relative_path = resource_file.path.replace("src/main/resources/", "") 50 | 51 | # Map src/main/resources/a/b/c.txt to a/b/c.txt. 52 | args.add(resource_file.path, format = "%s:" + relative_path) 53 | 54 | # Maybe compress code. 55 | if not ctx.attr.source_jar: 56 | # Deal with limitation of singlejar flags: tool's default behavior is 57 | # "no", but you get that behavior only by absence of compression flags. 58 | if ctx.attr.compress == "preserve": 59 | args.add("--dont_change_compression") 60 | elif ctx.attr.compress == "yes": 61 | args.add("--compression") 62 | elif ctx.attr.compress == "no": 63 | pass 64 | else: 65 | fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress) 66 | 67 | # Each package prefix has to be specified in its own --include_prefixes arg. 68 | for p in ctx.attr.root_packages: 69 | args.add("--include_prefixes", p.replace(".", "/")) 70 | 71 | if ctx.attr.exclude_build_data: 72 | args.add("--exclude_build_data") 73 | 74 | args.add_all("--deploy_manifest_lines", ctx.attr.manifest_lines, format_each = "\"%s\"") 75 | 76 | ctx.actions.run( 77 | inputs = inputs.to_list() + resource_files, 78 | outputs = [ctx.outputs.jar], 79 | arguments = [args], 80 | progress_message = "Merging into %s" % ctx.outputs.jar.short_path, 81 | mnemonic = "JavaSingleJar", 82 | executable = ctx.executable._singlejar, 83 | ) 84 | 85 | java_single_jar = rule( 86 | attrs = { 87 | "deps": attr.label_list(providers = [JavaInfo]), 88 | "resources": attr.label_list( 89 | providers = [JavaInfo], 90 | allow_files = True, 91 | ), 92 | "_singlejar": attr.label( 93 | default = Label("@bazel_tools//tools/jdk:singlejar"), 94 | cfg = "exec", 95 | allow_single_file = True, 96 | executable = True, 97 | ), 98 | "source_jar": attr.bool(default = False), 99 | "compress": attr.string(default = "preserve"), 100 | "root_packages": attr.string_list(), 101 | "exclude_build_data": attr.bool(default = True), 102 | "manifest_lines": attr.string_list(), 103 | }, 104 | outputs = { 105 | "jar": "%{name}.jar", 106 | }, 107 | implementation = _java_single_jar, 108 | doc = """ 109 | Collects Java dependencies and jar files into a single jar 110 | 111 | Args: 112 | deps: The Java targets (including java_import and java_library) to collect 113 | transitive dependencies from. Both compile-time dependencies (deps, 114 | exports) and runtime dependencies (runtime_deps) are collected. 115 | Resources are also collected. Native cc_library or java_wrap_cc 116 | dependencies are not. 117 | resources: A combination of resource files. Files must be stored in 118 | src/main/resources. Mapping rules: src/main/resources/a/b/c.txt will be 119 | copied to a/b/c.txt in the output jar. 120 | compress: Whether to always deflate ("yes"), always store ("no"), or pass 121 | through unmodified ("preserve"). The default is "preserve", and is the 122 | most efficient option -- no extra work is done to inflate or deflate. 123 | source_jar: Whether to combine only the source jars of input to create a single 124 | output source jar. The compiled code jars of input will be ignored. 125 | root_packages: Java packages to include in generated jar. 126 | exclude_build_data: Whether to omit the build-data.properties file generated 127 | by default. 128 | manifest_lines: lines to put in the output manifest file (manifest 129 | files in the input jars are ignored) 130 | 131 | Outputs: 132 | {name}.jar: A single jar containing all of the input. 133 | """, 134 | ) 135 | -------------------------------------------------------------------------------- /tools/javadoc.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Generates a Javadoc jar path/to/target/.jar. 17 | 18 | Arguments: 19 | srcs: source files to process. This might contain .java files or gen_rule that 20 | generates source jars. 21 | deps: targets that contain references to other types referenced in Javadoc. This can be the 22 | java_library/android_library target(s) for the same sources 23 | root_packages: Java packages to include in generated Javadoc. Any subpackages not listed in 24 | exclude_packages will be included as well 25 | exclude_packages: Java packages to exclude from generated Javadoc 26 | android_api_level: If Android APIs are used, the API level to compile against to generate 27 | Javadoc 28 | doctitle: title for Javadoc's index.html. See javadoc -doctitle 29 | bottom_text: text passed to javadoc's `-bottom` flag 30 | external_javadoc_links: a list of URLs that are passed to Javadoc's `-linkoffline` flag 31 | """ 32 | 33 | load("@rules_java//java:defs.bzl", "JavaInfo", "java_common") 34 | 35 | def _check_non_empty(value, name): 36 | if not value: 37 | fail("%s must be non-empty" % name) 38 | 39 | def _android_jar(android_api_level): 40 | if android_api_level == -1: 41 | return None 42 | return Label("@androidsdk//:platforms/android-%s/android.jar" % android_api_level) 43 | 44 | def _javadoc_library(ctx): 45 | _check_non_empty(ctx.attr.root_packages, "root_packages") 46 | 47 | transitive_deps = [dep[JavaInfo].transitive_compile_time_jars for dep in ctx.attr.deps] 48 | if ctx.attr._android_jar: 49 | transitive_deps.append(ctx.attr._android_jar.files) 50 | 51 | classpath = depset([], transitive = transitive_deps).to_list() 52 | 53 | include_packages = ":".join(ctx.attr.root_packages) 54 | javadoc_command = [ 55 | "%s/bin/javadoc" % ctx.attr._jdk[java_common.JavaRuntimeInfo].java_home, 56 | "-sourcepath srcs", 57 | "-use", 58 | "-subpackages", 59 | include_packages, 60 | "-encoding UTF8", 61 | "-classpath", 62 | ":".join([jar.path for jar in classpath]), 63 | "-notimestamp", 64 | "-d tmp", 65 | "-Xdoclint:-missing", 66 | "-quiet", 67 | ] 68 | 69 | if ctx.attr.doctitle: 70 | javadoc_command.append('-doctitle "%s"' % ctx.attr.doctitle) 71 | 72 | if ctx.attr.exclude_packages: 73 | javadoc_command.append("-exclude %s" % ":".join(ctx.attr.exclude_packages)) 74 | 75 | for link in ctx.attr.external_javadoc_links: 76 | javadoc_command.append("-linkoffline {0} {0}".format(link)) 77 | 78 | if ctx.attr.bottom_text: 79 | javadoc_command.append("-bottom '%s'" % ctx.attr.bottom_text) 80 | 81 | srcs = depset(transitive = [src.files for src in ctx.attr.srcs]).to_list() 82 | prepare_srcs_command = "mkdir srcs && " 83 | path_prefixes = [x.replace(".", "/") for x in ctx.attr.root_packages] 84 | for path_prefix in path_prefixes: 85 | prepare_srcs_command = "mkdir -p srcs/%s && " % (path_prefix) 86 | 87 | for src in srcs: 88 | if src.path.endswith(".jar"): 89 | prepare_srcs_command += "unzip -qq -B %s -d srcs && " % src.path 90 | elif src.path.endswith(".java"): 91 | for path_prefix in path_prefixes: 92 | if path_prefix in src.path: 93 | prepare_srcs_command += "cp %s srcs/%s && " % (src.path, path_prefix) 94 | 95 | jar_binary = "%s/bin/jar" % ctx.attr._jdk[java_common.JavaRuntimeInfo].java_home 96 | jar_command = "%s cf %s -C tmp ." % (jar_binary, ctx.outputs.jar.path) 97 | 98 | ctx.actions.run_shell( 99 | inputs = srcs + classpath + ctx.files._jdk, 100 | command = "%s %s && %s" % (prepare_srcs_command, " ".join(javadoc_command), jar_command), 101 | outputs = [ctx.outputs.jar], 102 | ) 103 | 104 | javadoc_library = rule( 105 | attrs = { 106 | "srcs": attr.label_list(allow_files = True), 107 | "deps": attr.label_list(), 108 | "doctitle": attr.string(default = ""), 109 | "root_packages": attr.string_list(), 110 | "exclude_packages": attr.string_list(), 111 | "android_api_level": attr.int(default = -1), 112 | "bottom_text": attr.string(default = ""), 113 | "external_javadoc_links": attr.string_list(), 114 | "_android_jar": attr.label( 115 | default = _android_jar, 116 | allow_single_file = True, 117 | ), 118 | "_jdk": attr.label( 119 | default = Label("@bazel_tools//tools/jdk:current_java_runtime"), 120 | allow_files = True, 121 | providers = [java_common.JavaRuntimeInfo], 122 | ), 123 | }, 124 | outputs = {"jar": "%{name}.jar"}, 125 | implementation = _javadoc_library, 126 | ) 127 | -------------------------------------------------------------------------------- /webpush/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) 6 | 7 | gen_maven_jar_rules( 8 | name = "maven", 9 | doctitle = "Tink Cryptography API for Message Encryption for Web Push (RFC 8291)", 10 | manifest_lines = [ 11 | "Automatic-Module-Name: com.google.crypto.tink.apps.webpush", 12 | ], 13 | root_packages = ["com.google.crypto.tink.apps.webpush"], 14 | deps = [ 15 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_constants", 16 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_decrypt", 17 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_encrypt", 18 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_util", 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | licenses(["notice"]) 4 | 5 | java_library( 6 | name = "web_push_hybrid_decrypt", 7 | srcs = ["WebPushHybridDecrypt.java"], 8 | deps = [ 9 | ":web_push_constants", 10 | ":web_push_util", 11 | "@maven//:com_google_crypto_tink_tink", 12 | "@maven//:com_google_errorprone_error_prone_annotations", 13 | ], 14 | ) 15 | 16 | java_library( 17 | name = "web_push_util", 18 | srcs = ["WebPushUtil.java"], 19 | deps = [ 20 | ":web_push_constants", 21 | "@maven//:com_google_crypto_tink_tink", 22 | ], 23 | ) 24 | 25 | java_library( 26 | name = "web_push_constants", 27 | srcs = ["WebPushConstants.java"], 28 | deps = ["@maven//:com_google_crypto_tink_tink"], 29 | ) 30 | 31 | java_library( 32 | name = "web_push_hybrid_encrypt", 33 | srcs = ["WebPushHybridEncrypt.java"], 34 | deps = [ 35 | ":web_push_constants", 36 | ":web_push_util", 37 | "@maven//:com_google_crypto_tink_tink", 38 | "@maven//:com_google_errorprone_error_prone_annotations", 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.webpush; 18 | 19 | import com.google.crypto.tink.subtle.EllipticCurves; 20 | import java.nio.charset.Charset; 21 | 22 | /** Various constants. */ 23 | final class WebPushConstants { 24 | static final Charset UTF_8 = Charset.forName("UTF-8"); 25 | static final int AUTH_SECRET_SIZE = 16; 26 | static final int IKM_SIZE = 32; 27 | static final int CEK_KEY_SIZE = 16; 28 | static final int NONCE_SIZE = 12; 29 | static final byte[] IKM_INFO = 30 | new byte[] {'W', 'e', 'b', 'P', 'u', 's', 'h', ':', ' ', 'i', 'n', 'f', 'o', (byte) 0}; 31 | static final byte[] CEK_INFO = 32 | new byte[] { 33 | 'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ', 34 | 'a', 'e', 's', '1', '2', '8', 'g', 'c', 'm', (byte) 0 35 | }; 36 | static final byte[] NONCE_INFO = 37 | new byte[] { 38 | 'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ', 39 | 'n', 'o', 'n', 'c', 'e', (byte) 0 40 | }; 41 | 42 | static final int SALT_SIZE = 16; 43 | static final int RECORD_SIZE_LEN = 4; 44 | static final int PUBLIC_KEY_SIZE_LEN = 1; 45 | static final int PUBLIC_KEY_SIZE = 65; 46 | // * salt: 16 47 | // * record size: 4 48 | // * public key size: 1 49 | // * uncompressed public key: 65 50 | // * total : 86 51 | static final int CONTENT_CODING_HEADER_SIZE = 52 | SALT_SIZE + RECORD_SIZE_LEN + PUBLIC_KEY_SIZE_LEN + PUBLIC_KEY_SIZE; 53 | 54 | // the byte 0x2 separating the payload and the padding 55 | static final byte PADDING_DELIMITER_BYTE = (byte) 2; 56 | static final int PADDING_DELIMETER_SIZE = 1; 57 | static final int DEFAULT_PADDING_SIZE = 0; 58 | static final int TAG_SIZE = 16; 59 | // * content coding header: 86 60 | // * padding delimeter: 1 61 | // * AES-GCM tag size: 16 62 | // * Total: 103 63 | static final int CIPHERTEXT_OVERHEAD = 64 | CONTENT_CODING_HEADER_SIZE + PADDING_DELIMETER_SIZE + DEFAULT_PADDING_SIZE + TAG_SIZE; 65 | 66 | static final int MAX_CIPHERTEXT_SIZE = 4096; 67 | 68 | static final String HMAC_SHA256 = "HMACSHA256"; 69 | static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT = 70 | EllipticCurves.PointFormatType.UNCOMPRESSED; 71 | static final EllipticCurves.CurveType NIST_P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256; 72 | 73 | private WebPushConstants() {} 74 | } 75 | -------------------------------------------------------------------------------- /webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.webpush; 18 | 19 | import com.google.crypto.tink.subtle.Bytes; 20 | import com.google.crypto.tink.subtle.Hkdf; 21 | import java.security.GeneralSecurityException; 22 | 23 | /** Various helpers. */ 24 | final class WebPushUtil { 25 | public static byte[] computeIkm( 26 | final byte[] ecdhSecret, 27 | final byte[] authSecret, 28 | final byte[] uaPublic, 29 | final byte[] asPublic) 30 | throws GeneralSecurityException { 31 | byte[] keyInfo = Bytes.concat(WebPushConstants.IKM_INFO, uaPublic, asPublic); 32 | return Hkdf.computeHkdf( 33 | WebPushConstants.HMAC_SHA256, 34 | ecdhSecret /* ikm */, 35 | authSecret /* salt */, 36 | keyInfo, 37 | WebPushConstants.IKM_SIZE); 38 | } 39 | 40 | public static byte[] computeCek(final byte[] ikm, final byte[] salt) 41 | throws GeneralSecurityException { 42 | return Hkdf.computeHkdf( 43 | WebPushConstants.HMAC_SHA256, 44 | ikm, 45 | salt, 46 | WebPushConstants.CEK_INFO, 47 | WebPushConstants.CEK_KEY_SIZE); 48 | } 49 | 50 | public static byte[] computeNonce(final byte[] ikm, final byte[] salt) 51 | throws GeneralSecurityException { 52 | return Hkdf.computeHkdf( 53 | WebPushConstants.HMAC_SHA256, 54 | ikm, 55 | salt, 56 | WebPushConstants.NONCE_INFO, 57 | WebPushConstants.NONCE_SIZE); 58 | } 59 | 60 | private WebPushUtil() {} 61 | } 62 | -------------------------------------------------------------------------------- /webpush/src/test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//tools:gen_java_test_rules.bzl", "gen_java_test_rules") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) 6 | 7 | # Tests 8 | 9 | java_library( 10 | name = "generator_test", 11 | testonly = 1, 12 | srcs = glob([ 13 | "**/*.java", 14 | ]), 15 | deps = [ 16 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_constants", 17 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_decrypt", 18 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_encrypt", 19 | "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_util", 20 | "@maven//:com_google_crypto_tink_tink", 21 | "@maven//:junit_junit", 22 | ], 23 | ) 24 | 25 | gen_java_test_rules( 26 | test_files = glob([ 27 | "**/*Test.java", 28 | ]), 29 | deps = [ 30 | ":generator_test", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | package com.google.crypto.tink.apps.webpush; 18 | 19 | import static org.junit.Assert.assertArrayEquals; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.fail; 22 | 23 | import com.google.crypto.tink.HybridDecrypt; 24 | import com.google.crypto.tink.HybridEncrypt; 25 | import com.google.crypto.tink.subtle.Base64; 26 | import com.google.crypto.tink.subtle.EllipticCurves; 27 | import com.google.crypto.tink.subtle.Random; 28 | import java.security.GeneralSecurityException; 29 | import java.security.KeyPair; 30 | import java.security.interfaces.ECPrivateKey; 31 | import java.security.interfaces.ECPublicKey; 32 | import java.util.Arrays; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | import org.junit.runners.JUnit4; 36 | 37 | /** Unit tests for {@code WebPushHybridDecrypt}. */ 38 | @RunWith(JUnit4.class) 39 | public class WebPushHybridDecryptTest { 40 | // Copied from https://tools.ietf.org/html/rfc8291#section-5. 41 | private static final String PLAINTEXT = "V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24"; 42 | private static final String RECEIVER_PRIVATE_KEY = "q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94"; 43 | private static final String RECEIVER_PUBLIC_KEY = 44 | "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4"; 45 | private static final String AUTH_SECRET = "BTBZMqHH6r4Tts7J_aSIgg"; 46 | private static final String CIPHERTEXT = 47 | "DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27ml" 48 | + "mlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPT" 49 | + "pK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN"; 50 | private static final int RECORD_SIZE = 4096; 51 | 52 | @Test 53 | public void testWithRfc8291TestVector() throws Exception { 54 | byte[] plaintext = Base64.urlSafeDecode(PLAINTEXT); 55 | byte[] recipientPrivateKey = Base64.urlSafeDecode(RECEIVER_PRIVATE_KEY); 56 | byte[] recipientPublicKey = Base64.urlSafeDecode(RECEIVER_PUBLIC_KEY); 57 | byte[] authSecret = Base64.urlSafeDecode(AUTH_SECRET); 58 | byte[] ciphertext = Base64.urlSafeDecode(CIPHERTEXT); 59 | 60 | HybridDecrypt hybridDecrypt = 61 | new WebPushHybridDecrypt.Builder() 62 | .withRecordSize(RECORD_SIZE) 63 | .withAuthSecret(authSecret) 64 | .withRecipientPublicKey(recipientPublicKey) 65 | .withRecipientPrivateKey(recipientPrivateKey) 66 | .build(); 67 | assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */)); 68 | } 69 | 70 | @Test 71 | public void testEncryptDecryptWithInvalidRecordSizes() throws Exception { 72 | KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); 73 | ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); 74 | ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); 75 | byte[] authSecret = Random.randBytes(16); 76 | 77 | // Test with out of range record sizes. 78 | { 79 | try { 80 | new WebPushHybridDecrypt.Builder() 81 | .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1) 82 | .withAuthSecret(authSecret) 83 | .withRecipientPublicKey(uaPublicKey) 84 | .withRecipientPrivateKey(uaPrivateKey) 85 | .build(); 86 | fail("Expected IllegalArgumentException"); 87 | } catch (IllegalArgumentException ex) { 88 | // expected. 89 | } 90 | 91 | try { 92 | Object unused = 93 | new WebPushHybridDecrypt.Builder() 94 | .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1) 95 | .withAuthSecret(authSecret) 96 | .withRecipientPublicKey(uaPublicKey) 97 | .withRecipientPrivateKey(uaPrivateKey) 98 | .build(); 99 | fail("Expected IllegalArgumentException"); 100 | } catch (IllegalArgumentException ex) { 101 | // expected. 102 | } 103 | } 104 | 105 | // Test with random mismatched record size. 106 | { 107 | for (int i = 0; i < 50; i++) { 108 | int recordSize = 109 | WebPushConstants.CIPHERTEXT_OVERHEAD 110 | + Random.randInt( 111 | WebPushConstants.MAX_CIPHERTEXT_SIZE 112 | - WebPushConstants.CIPHERTEXT_OVERHEAD 113 | - 1); 114 | HybridEncrypt hybridEncrypt = 115 | new WebPushHybridEncrypt.Builder() 116 | .withRecordSize(recordSize) 117 | .withAuthSecret(authSecret) 118 | .withRecipientPublicKey(uaPublicKey) 119 | .build(); 120 | HybridDecrypt hybridDecrypt = 121 | new WebPushHybridDecrypt.Builder() 122 | .withRecordSize(recordSize + 1) 123 | .withAuthSecret(authSecret) 124 | .withRecipientPublicKey(uaPublicKey) 125 | .withRecipientPrivateKey(uaPrivateKey) 126 | .build(); 127 | byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD); 128 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */); 129 | 130 | try { 131 | hybridDecrypt.decrypt(ciphertext, null /* contextInfo */); 132 | fail("Expected GeneralSecurityException"); 133 | } catch (GeneralSecurityException ex) { 134 | // expected. 135 | } 136 | } 137 | } 138 | } 139 | 140 | @Test 141 | public void testNonNullContextInfo() throws Exception { 142 | KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); 143 | ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); 144 | ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); 145 | byte[] authSecret = Random.randBytes(16); 146 | 147 | HybridEncrypt hybridEncrypt = 148 | new WebPushHybridEncrypt.Builder() 149 | .withAuthSecret(authSecret) 150 | .withRecipientPublicKey(uaPublicKey) 151 | .build(); 152 | HybridDecrypt hybridDecrypt = 153 | new WebPushHybridDecrypt.Builder() 154 | .withAuthSecret(authSecret) 155 | .withRecipientPublicKey(uaPublicKey) 156 | .withRecipientPrivateKey(uaPrivateKey) 157 | .build(); 158 | byte[] plaintext = Random.randBytes(20); 159 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */); 160 | try { 161 | byte[] contextInfo = new byte[0]; 162 | hybridDecrypt.decrypt(ciphertext, contextInfo); 163 | fail("Expected GeneralSecurityException"); 164 | } catch (GeneralSecurityException ex) { 165 | // expected; 166 | } 167 | } 168 | 169 | @Test 170 | public void testModifyCiphertext() throws Exception { 171 | KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); 172 | ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); 173 | ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); 174 | byte[] authSecret = Random.randBytes(16); 175 | 176 | HybridEncrypt hybridEncrypt = 177 | new WebPushHybridEncrypt.Builder() 178 | .withAuthSecret(authSecret) 179 | .withRecipientPublicKey(uaPublicKey) 180 | .build(); 181 | HybridDecrypt hybridDecrypt = 182 | new WebPushHybridDecrypt.Builder() 183 | .withAuthSecret(authSecret) 184 | .withRecipientPublicKey(uaPublicKey) 185 | .withRecipientPrivateKey(uaPrivateKey) 186 | .build(); 187 | byte[] plaintext = Random.randBytes(20); 188 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */); 189 | 190 | // Flipping bits. 191 | for (int b = 0; b < ciphertext.length; b++) { 192 | for (int bit = 0; bit < 8; bit++) { 193 | byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length); 194 | modified[b] ^= (byte) (1 << bit); 195 | try { 196 | byte[] unused = hybridDecrypt.decrypt(modified, null /* contextInfo */); 197 | fail("Decrypting modified ciphertext should fail"); 198 | } catch (GeneralSecurityException ex) { 199 | // This is expected. 200 | } 201 | } 202 | } 203 | 204 | // Truncate the message. 205 | for (int length = 0; length < ciphertext.length; length++) { 206 | byte[] modified = Arrays.copyOf(ciphertext, length); 207 | try { 208 | byte[] unused = hybridDecrypt.decrypt(modified, null /* contextInfo */); 209 | fail("Decrypting modified ciphertext should fail"); 210 | } catch (GeneralSecurityException ex) { 211 | // This is expected. 212 | } 213 | } 214 | } 215 | 216 | @Test 217 | public void testEncryptDecrypt_withPadding_shouldWork() throws Exception { 218 | KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE); 219 | ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate(); 220 | ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic(); 221 | byte[] authSecret = Random.randBytes(16); 222 | 223 | int paddingSize = 20; 224 | int plaintextSize = 20; 225 | HybridEncrypt hybridEncrypt = 226 | new WebPushHybridEncrypt.Builder() 227 | .withAuthSecret(authSecret) 228 | .withPaddingSize(paddingSize) 229 | .withRecipientPublicKey(uaPublicKey) 230 | .build(); 231 | HybridDecrypt hybridDecrypt = 232 | new WebPushHybridDecrypt.Builder() 233 | .withAuthSecret(authSecret) 234 | .withRecipientPublicKey(uaPublicKey) 235 | .withRecipientPrivateKey(uaPrivateKey) 236 | .build(); 237 | byte[] plaintext = Random.randBytes(plaintextSize); 238 | byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */); 239 | 240 | assertEquals( 241 | ciphertext.length, plaintext.length + paddingSize + WebPushConstants.CIPHERTEXT_OVERHEAD); 242 | assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */)); 243 | } 244 | } 245 | --------------------------------------------------------------------------------