Merge commit '053b5b8c4214ec379daac2cc2bf208c51cc1cd66' into main
Current Tink mirror in Fuchsia has missing include issue with the latest version of Clang. The missing include is #include <cstdlib> (https://github.com/tink-crypto/tink/blob/master/cc/util/secret_data_internal.h#L21)
Tink introduced it in https://github.com/tink-crypto/tink/commit/053b5b8c4214ec379daac2cc2bf208c51cc1cd66
We should at least uprev Tink to this commit to unblock the issue.
Command to uprev Tink:
```
git checkout origin/main -b ${USER}-merge
git merge 053b5b8c4214ec379daac2cc2bf208c51cc1cd66
python3 ./tools/convert_for_cobalt
fx format-code --files=${gn build files that convert_for_cobalt wrote}
git commit --no-verify
```
Locally patch to include missing absl/strings/str_cat.h header:
cc/mac/hmac_parameters.cc
cc/mac/aes_cmac_parameters.cc
Bug: 376297654
Change-Id: I7cc47556e5ddec6be01bd7a523f527abea47e20a
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/tink/+/1153894
Commit-Queue: Anivia Li <aniviali@google.com>
Reviewed-by: Alex Pankhurst <pankhurst@google.com>
diff --git a/.bazelversion b/.bazelversion
index ac14c3d..09b254e 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-5.1.1
+6.0.0
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 66f2ffc..2f37316 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,7 +1,6 @@
---
name: Bug report
about: Create a report to help us improve Tink
-assignees: 'thaidn, chuckx'
---
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 486da6f..100d12c 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,7 +1,6 @@
---
name: Feature request
about: Suggest an idea for Tink
-assignees: 'thaidn, chuckx'
---
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 49711e3..49269d9 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -32,7 +32,7 @@
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -45,7 +45,7 @@
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v2
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -70,4 +70,4 @@
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dff0033..2e4f78f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
-cmake_minimum_required(VERSION 3.5)
-project(Tink VERSION 1.7.0 LANGUAGES CXX)
+cmake_minimum_required(VERSION 3.13)
+project(Tink VERSION 2.0.0 LANGUAGES CXX)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
@@ -7,8 +7,6 @@
option(TINK_USE_SYSTEM_OPENSSL "Build Tink linking to OpenSSL installed in the system" OFF)
option(TINK_USE_INSTALLED_ABSEIL "Build Tink linking to Abseil installed in the system" OFF)
option(TINK_USE_INSTALLED_GOOGLETEST "Build Tink linking to GTest installed in the system" OFF)
-option(TINK_USE_ABSL_STATUS "Compile Tink with absl::Status" OFF)
-option(TINK_USE_ABSL_STATUSOR "Compile Tink with absl::StatusOr" OFF)
option(USE_ONLY_FIPS "Enables the FIPS only mode in Tink" OFF)
set(CPACK_GENERATOR TGZ)
diff --git a/README.md b/README.md
index 74035b9..3ff7337 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,74 @@
# Tink
-*A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.*
+*A multi-language, cross-platform library that provides cryptographic APIs that
+are secure, easy to use correctly, and hard(er) to misuse. See also:
+https://developers.google.com/tink*.
-https://developers.google.com/tink
+> **NOTE**: **Tink is moving!**
+>
+> As part of our roadmap we are splitting Tink into
+> [multiple GitHub repositories][split_repo_roadmap_url] that will be hosted at
+> [github.com/tink-crypto](https://github.com/tink-crypto) and will be
+> independently versioned.
+>
+> Roughly, we are going to create one repository per language, library extension
+> such as KMS (except Tink Python), and tools.
+>
+> A few important highlights:
+>
+> - The migration will be done gradually over the course of 2023 with a new
+> release from each of the new repositories. Releases will be announced in
+> our [mailing list][tink_mailing_list_url].
+> - We will keep updating each implementation/tool in
+> [github.com/google/tink](https://github.com/google/tink) for a specified
+> amount of time; migrated implementations/tools will eventually stop being
+> updated on [github.com/google/tink](https://github.com/google/tink). The
+> support window depends on the specific implementation, as shown in the
+> table below.
+> - New issues and pull requests should be created in the new repos.
+>
+> Below is the list of resulting repositories, migration timeline and expected
+> end of support.
+>
+> Tink implementation/extension | New repository | Migration status | End of support in google/tink
+> ------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------ | -----------------------------
+> Tink Java | [tink-crypto/tink-java](https://github.com/tink-crypto/tink-java) | Complete (Q1 2023) | Q3 2023
+> Tink Java AWS KMS extension | [tink-crypto/tink-java-awskms](https://github.com/tink-crypto/tink-java-awskms) | Complete (Q1 2023) | Q3 2023
+> Tink Java Google Cloud KMS extension | [tink-crypto/tink-java-gcpkms](https://github.com/tink-crypto/tink-java-gcpkms) | Complete (Q1 2023) | Q3 2023
+> Tink Java apps extension | [tink-crypto/tink-java-apps](https://github.com/tink-crypto/tink-java-apps) | Complete (Q1 2023) | Q3 2023
+> Tink C++ | [tink-crypto/tink-cc](https://github.com/tink-crypto/tink-cc) | Complete (Q2 2023) | Q4 2023
+> Tink C++ AWS KMS extension | [tink-crypto/tink-cc-awskms](https://github.com/tink-crypto/tink-cc-awskms) | Complete (Q2 2023) | Q4 2023
+> Tink C++ Google Cloud KMS extension | [tink-crypto/tink-cc-gcpkms](https://github.com/tink-crypto/tink-cc-gcpkms) | Complete (Q2 2023) | Q4 2023
+> Tink Python | [tink-crypto/tink-py](https://github.com/tink-crypto/tink-py) | Not started (expected Q3 2023) | TBA
+> Tink Go | [tink-crypto/tink-go](https://github.com/tink-crypto/tink-go) | In progress (expected Q2 2023) | TBA
+> Tink Go AWS KMS extension | [tink-crypto/tink-go-awskms](https://github.com/tink-crypto/tink-go-awskms) | In progress (expected Q2 2023) | TBA
+> Tink Go Google Cloud KMS extension | [tink-crypto/tink-go-gcpkms](https://github.com/tink-crypto/tink-go-gcpkms) | In progress (expected Q2 2023) | TBA
+> Tink Go HashiCorp Vault KMS extension | [tink-crypto/tink-go-hcvault](https://github.com/tink-crypto/tink-go-hcvault) | In progress (expected Q2 2023) | TBA
+> Tink Obj-C | [tink-crypto/tink-objc](https://github.com/tink-crypto/tink-objc) | Not started (expected Q4 2023) | TBA
+> Tink Tinkey | [tink-crypto/tink-tinkey](https://github.com/tink-crypto/tink-tinkey) | Complete (Q2 2023) | Q4 2023
+> Tink cross language tests | [tink-crypto/tink-cross-lang-tests](https://github.com/tink-crypto/tink-cross-lang-tests) | Not started (expected Q4 2023) | TBA
-**`Ubuntu`** | **`macOS`**
------------------------------------ | ---------------------------------
-[![Kokoro Ubuntu][ubuntu_badge]](#) | [![Kokoro macOS][macos_badge]](#)
+> **NOTE**: **We are removing Tink for JavaScript/TypeScript**
+>
+> We are removing the Tink JavaScript/TypeScript library from our current Github
+> repository (master branch). As part of our effort to migrate Tink to
+> https://github.com/tink-crypto, we will not release an individual
+> JavaScript/Typescript repository. Furthermore, the JavaScript/TypeScript
+> [directory](https://github.com/google/tink/tree/master/javascript) in the
+> current release branch (v1.7.0) will no longer be actively supported.
+>
+> _We aim to remove the JS/TS directory from the current Tink Github repository
+> (master branch) on **June 22, 2023**. We will also deprecate the Tink npm
+> package on this date._
+>
+> See [this](https://github.com/google/tink/issues/689) tracking issue for more
+> details.
+>
+> Feel free to use our [mailing list][tink_mailing_list_url] to raise any
+> questions, issues or concerns.
-[ubuntu_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png
-[macos_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png
+[split_repo_roadmap_url]: https://developers.google.com/tink/roadmap#splitting_tink_into_multiple_github_repositories
+[tink_mailing_list_url]: https://groups.google.com/forum/#!forum/tink-users
## Index
@@ -25,9 +84,10 @@
Using crypto in your application [shouldn't have to][devs_are_users_too_slides]
feel like juggling chainsaws in the dark. Tink is a crypto library written by a
group of cryptographers and security engineers at Google. It was born out of our
-extensive experience working with Google's product teams, [fixing weaknesses in
-implementations](https://github.com/google/wycheproof), and providing simple
-APIs that can be used safely without needing a crypto background.
+extensive experience working with Google's product teams,
+[fixing weaknesses in implementations](https://github.com/google/wycheproof),
+and providing simple APIs that can be used safely without needing a crypto
+background.
Tink provides secure APIs that are easy to use correctly and hard(er) to misuse.
It reduces common crypto pitfalls with user-centered design, careful
@@ -52,6 +112,15 @@
released on 2022-08-09.
Javascript/Typescript is in an alpha state and should only be used for testing.
+Please see the intent to remove statement
+[here](https://github.com/google/tink/issues/689).
+
+**`Ubuntu`** | **`macOS`**
+----------------------------------- | ---------------------------------
+[![Kokoro Ubuntu][ubuntu_badge]](#) | [![Kokoro macOS][macos_badge]](#)
+
+[ubuntu_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png
+[macos_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png
## Getting started
@@ -130,12 +199,11 @@
## Contact and mailing list
-If you want to contribute, please read [CONTRIBUTING](docs/CONTRIBUTING.md)
-and send us pull requests. You can also report bugs or file feature requests.
+If you want to contribute, please read [CONTRIBUTING](docs/CONTRIBUTING.md) and
+send us pull requests. You can also report bugs or file feature requests.
If you'd like to talk to the developers or get notified about major product
-updates, you may want to subscribe to our
-[mailing list](https://groups.google.com/forum/#!forum/tink-users).
+updates, you may want to subscribe to our [mailing list][tink_mailing_list_url].
## Maintainers
@@ -167,3 +235,5 @@
- Enzo Puig
- Veronika Slívová
- Paula Vidas
+- Cathie Yun
+- Federico Zalcberg
diff --git a/WORKSPACE b/WORKSPACE
deleted file mode 100644
index 5a9185b..0000000
--- a/WORKSPACE
+++ /dev/null
@@ -1,9 +0,0 @@
-workspace(name = "tink_base")
-
-load("@tink_base//:tink_base_deps.bzl", "tink_base_deps")
-
-tink_base_deps()
-
-load("@tink_base//:tink_base_deps_init.bzl", "tink_base_deps_init")
-
-tink_base_deps_init()
diff --git a/apps/.bazelversion b/apps/.bazelversion
deleted file mode 100644
index ac14c3d..0000000
--- a/apps/.bazelversion
+++ /dev/null
@@ -1 +0,0 @@
-5.1.1
diff --git a/apps/BUILD.bazel b/apps/BUILD.bazel
deleted file mode 100644
index 1a1b3a3..0000000
--- a/apps/BUILD.bazel
+++ /dev/null
@@ -1,3 +0,0 @@
-package(default_visibility = ["//:__subpackages__"])
-
-licenses(["notice"])
diff --git a/apps/README.md b/apps/README.md
deleted file mode 100644
index 70b329f..0000000
--- a/apps/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Tink Java Apps
-
-It contains extensions and applications of Tink Java.
diff --git a/apps/WORKSPACE b/apps/WORKSPACE
deleted file mode 100644
index 27aaa48..0000000
--- a/apps/WORKSPACE
+++ /dev/null
@@ -1,22 +0,0 @@
-workspace(name = "tink_apps")
-
-local_repository(
- name = "tink_java",
- path = "../java_src",
-)
-
-load("@tink_java//:tink_java_deps.bzl", "tink_java_deps", "TINK_MAVEN_ARTIFACTS")
-tink_java_deps()
-
-load("@tink_java//:tink_java_deps_init.bzl", "tink_java_deps_init")
-tink_java_deps_init()
-
-load("@rules_jvm_external//:defs.bzl", "maven_install")
-
-maven_install(
- artifacts = TINK_MAVEN_ARTIFACTS,
- repositories = [
- "https://maven.google.com",
- "https://repo1.maven.org/maven2",
- ],
-)
diff --git a/apps/paymentmethodtoken/BUILD.bazel b/apps/paymentmethodtoken/BUILD.bazel
deleted file mode 100644
index f601945..0000000
--- a/apps/paymentmethodtoken/BUILD.bazel
+++ /dev/null
@@ -1,26 +0,0 @@
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-gen_maven_jar_rules(
- name = "maven",
- doctitle = "Tink Cryptography API for Google Payment Method Token",
- manifest_lines = [
- "Automatic-Module-Name: com.google.crypto.tink.apps.paymentmethodtoken",
- ],
- root_packages = ["com.google.crypto.tink.apps.paymentmethodtoken"],
- deps = [
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:google_payments_public_keys_manager",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_constants",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_decrypt",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_encrypt",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_kem",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_key_gen",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_sender",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_util",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:sender_intermediate_cert_factory",
- ],
-)
diff --git a/apps/paymentmethodtoken/README.md b/apps/paymentmethodtoken/README.md
deleted file mode 100644
index 936026c..0000000
--- a/apps/paymentmethodtoken/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# An implementation of [Google Payment Method Token](https://developers.google.com/pay/api/payment-data-cryptography).
-
-## Latest release
-
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-paymentmethodtoken/1.7.0).
-
-The Maven group ID is `com.google.crypto.tink`, and the artifact ID is
-`apps-paymentmethodtoken`.
-
-To add a dependency using Maven:
-
-```xml
-<dependency>
- <groupId>com.google.crypto.tink</groupId>
- <artifactId>apps-paymentmethodtoken</artifactId>
- <version>1.7.0</version>
-</dependency>
-```
-
-## Snapshots
-
-Snapshots of this app built from the master branch are available through Maven
-using version `HEAD-SNAPSHOT`. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-paymentmethodtoken/HEAD-SNAPSHOT).
-
-To add a dependency using Maven:
-
-```xml
-<repositories>
-<repository>
- <id>sonatype-snapshots</id>
- <name>sonatype-snapshots</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
- <snapshots>
- <enabled>true</enabled>
- <updatePolicy>always</updatePolicy>
- </snapshots>
- <releases>
- <updatePolicy>always</updatePolicy>
- </releases>
-</repository>
-</repositories>
-
-<dependency>
- <groupId>com.google.crypto.tink</groupId>
- <artifactId>apps-paymentmethodtoken</artifactId>
- <version>HEAD-SNAPSHOT</version>
-</dependency>
-```
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel
deleted file mode 100644
index 434db56..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel
+++ /dev/null
@@ -1,125 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-java_library(
- name = "google_payments_public_keys_manager",
- srcs = ["GooglePaymentsPublicKeysManager.java"],
- deps = [
- "@maven//:com_google_http_client_google_http_client",
- "@tink_java//src/main/java/com/google/crypto/tink/util:keys_downloader",
- ],
-)
-
-java_library(
- name = "payment_method_token_hybrid_decrypt",
- srcs = ["PaymentMethodTokenHybridDecrypt.java"],
- deps = [
- ":payment_method_token_constants",
- ":payment_method_token_recipient_kem",
- ":payment_method_token_util",
- "@maven//:com_google_code_gson_gson",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:bytes",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:hkdf",
- ],
-)
-
-java_library(
- name = "payment_method_token_sender",
- srcs = ["PaymentMethodTokenSender.java"],
- deps = [
- ":payment_method_token_constants",
- ":payment_method_token_hybrid_encrypt",
- ":payment_method_token_util",
- "@maven//:com_google_code_gson_gson",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
- "@tink_java//src/main/java/com/google/crypto/tink:public_key_sign",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- ],
-)
-
-java_library(
- name = "payment_method_token_recipient_key_gen",
- srcs = ["PaymentMethodTokenRecipientKeyGen.java"],
- deps = [
- ":payment_method_token_constants",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- ],
-)
-
-java_library(
- name = "payment_method_token_constants",
- srcs = ["PaymentMethodTokenConstants.java"],
- deps = [
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:enums",
- ],
-)
-
-java_library(
- name = "payment_method_token_recipient_kem",
- srcs = ["PaymentMethodTokenRecipientKem.java"],
-)
-
-java_library(
- name = "payment_method_token_hybrid_encrypt",
- srcs = ["PaymentMethodTokenHybridEncrypt.java"],
- deps = [
- ":payment_method_token_constants",
- ":payment_method_token_util",
- "@maven//:com_google_code_gson_gson",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecies_hkdf_sender_kem",
- ],
-)
-
-java_library(
- name = "payment_method_token_recipient",
- srcs = ["PaymentMethodTokenRecipient.java"],
- deps = [
- ":google_payments_public_keys_manager",
- ":payment_method_token_constants",
- ":payment_method_token_hybrid_decrypt",
- ":payment_method_token_recipient_kem",
- ":payment_method_token_util",
- "@maven//:com_google_code_gson_gson",
- "@maven//:joda_time_joda_time",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- ],
-)
-
-java_library(
- name = "sender_intermediate_cert_factory",
- srcs = ["SenderIntermediateCertFactory.java"],
- deps = [
- ":payment_method_token_constants",
- ":payment_method_token_util",
- "@maven//:com_google_code_gson_gson",
- "@tink_java//src/main/java/com/google/crypto/tink:public_key_sign",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- ],
-)
-
-java_library(
- name = "payment_method_token_util",
- srcs = ["PaymentMethodTokenUtil.java"],
- deps = [
- ":payment_method_token_constants",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:bytes",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
- ],
-)
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java
deleted file mode 100644
index 0da08d2..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.api.client.http.HttpTransport;
-import com.google.api.client.http.javanet.NetHttpTransport;
-import com.google.crypto.tink.util.KeysDownloader;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * Thread-safe Google Payments public key manager.
- *
- * <p>For best performance, use the {@link GooglePaymentsPublicKeysManager#INSTANCE_PRODUCTION} for
- * production environment or {@link GooglePaymentsPublicKeysManager#INSTANCE_TEST} for test
- * environment.
- *
- * <p>If you need extra customizations for your use, we recommend you to use {@link
- * GooglePaymentsPublicKeysManager.Builder} to construct an instance and keep it as a singleton in a
- * static final variable across requests.
- *
- * <p>When initializing your server, we also recommend that you call {@link #refreshInBackground()}
- * to proactively fetch the keys.
- *
- * @since 1.0.0
- */
-public class GooglePaymentsPublicKeysManager {
- /** Default HTTP transport used by this class. */
- public static final NetHttpTransport DEFAULT_HTTP_TRANSPORT =
- new NetHttpTransport.Builder().build();
- /** URL to fetch keys for environment production. */
- public static final String KEYS_URL_PRODUCTION =
- "https://payments.developers.google.com/paymentmethodtoken/keys.json";
- /** URL to fetch keys for environment test. */
- public static final String KEYS_URL_TEST =
- "https://payments.developers.google.com/paymentmethodtoken/test/keys.json";
-
- private static final Executor DEFAULT_BACKGROUND_EXECUTOR = Executors.newCachedThreadPool();
-
- private final KeysDownloader downloader;
-
- /**
- * Instance configured to talk to fetch keys from production environment (from {@link
- * GooglePaymentsPublicKeysManager#KEYS_URL_PRODUCTION}).
- */
- public static final GooglePaymentsPublicKeysManager INSTANCE_PRODUCTION =
- new GooglePaymentsPublicKeysManager(
- DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_PRODUCTION);
- /**
- * Instance configured to talk to fetch keys from test environment (from {@link
- * GooglePaymentsPublicKeysManager#KEYS_URL_TEST}).
- */
- public static final GooglePaymentsPublicKeysManager INSTANCE_TEST =
- new GooglePaymentsPublicKeysManager(
- DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_TEST);
-
- GooglePaymentsPublicKeysManager(
- Executor backgroundExecutor, HttpTransport httpTransport, String keysUrl) {
- this.downloader =
- new KeysDownloader.Builder()
- .setUrl(keysUrl)
- .setExecutor(backgroundExecutor)
- .setHttpTransport(httpTransport)
- .build();
- }
-
- HttpTransport getHttpTransport() {
- return downloader.getHttpTransport();
- }
-
- String getUrl() {
- return downloader.getUrl();
- }
-
- /**
- * Returns a string containing a JSON with the Google public signing keys.
- *
- * <p>Meant to be called by {@link PaymentMethodTokenRecipient}.
- */
- String getTrustedSigningKeysJson() throws IOException {
- return this.downloader.download();
- }
-
- /** Fetches keys in the background. */
- public void refreshInBackground() {
- downloader.refreshInBackground();
- }
-
- /**
- * Builder for {@link GooglePaymentsPublicKeysManager}.
- *
- * @since 1.0.0
- */
- public static class Builder {
- private HttpTransport httpTransport = DEFAULT_HTTP_TRANSPORT;
- private String keysUrl = KEYS_URL_PRODUCTION;
-
- public Builder setKeysUrl(String keysUrl) {
- this.keysUrl = keysUrl;
- return this;
- }
-
- /**
- * Sets the HTTP transport.
- *
- * <p>You generally should not need to set a custom transport as the default transport {@link
- * GooglePaymentsPublicKeysManager#DEFAULT_HTTP_TRANSPORT} should be suited for most use cases.
- */
- public Builder setHttpTransport(HttpTransport httpTransport) {
- this.httpTransport = httpTransport;
- return this;
- }
-
- public GooglePaymentsPublicKeysManager build() {
- // If all parameters are equal to the existing singleton instances, returning them instead.
- // This is more a safe guard if users of this class construct a new class and forget to
- // save in a singleton.
- for (GooglePaymentsPublicKeysManager instance :
- Arrays.asList(INSTANCE_PRODUCTION, INSTANCE_TEST)) {
- if (instance.getHttpTransport() == httpTransport && instance.getUrl().equals(keysUrl)) {
- return instance;
- }
- }
- return new GooglePaymentsPublicKeysManager(
- DEFAULT_BACKGROUND_EXECUTOR, httpTransport, keysUrl);
- }
- }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java
deleted file mode 100644
index 416a426..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Enums.HashType;
-import java.nio.charset.StandardCharsets;
-
-/** Various constants. */
-final class PaymentMethodTokenConstants {
- public static final String GOOGLE_SENDER_ID = "Google";
- public static final String HMAC_SHA256_ALGO = "HmacSha256";
- public static final byte[] HKDF_EMPTY_SALT = new byte[0];
- public static final byte[] GOOGLE_CONTEXT_INFO_ECV1 = "Google".getBytes(StandardCharsets.UTF_8);
- public static final String AES_CTR_ALGO = "AES/CTR/NoPadding";
- // Zero IV is fine here because each encryption uses a unique key.
- public static final byte[] AES_CTR_ZERO_IV = new byte[16];
- public static final EllipticCurves.CurveType P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256;
- public static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT =
- EllipticCurves.PointFormatType.UNCOMPRESSED;
- public static final String PROTOCOL_VERSION_EC_V1 = "ECv1";
- public static final String PROTOCOL_VERSION_EC_V2 = "ECv2";
- public static final String PROTOCOL_VERSION_EC_V2_SIGNING_ONLY = "ECv2SigningOnly";
- public static final HashType ECDSA_HASH_SHA256 = HashType.SHA256;
-
- public static final String JSON_ENCRYPTED_MESSAGE_KEY = "encryptedMessage";
- public static final String JSON_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey";
- public static final String JSON_INTERMEDIATE_SIGNING_KEY = "intermediateSigningKey";
- public static final String JSON_KEY_EXPIRATION_KEY = "keyExpiration";
- public static final String JSON_KEY_VALUE_KEY = "keyValue";
- public static final String JSON_MESSAGE_EXPIRATION_KEY = "messageExpiration";
- public static final String JSON_PROTOCOL_VERSION_KEY = "protocolVersion";
- public static final String JSON_SIGNATURES_KEY = "signatures";
- public static final String JSON_SIGNATURE_KEY = "signature";
- public static final String JSON_SIGNED_KEY_KEY = "signedKey";
- public static final String JSON_SIGNED_MESSAGE_KEY = "signedMessage";
- public static final String JSON_TAG_KEY = "tag";
-
- /** Represents configuration regarding each protocol version. */
- enum ProtocolVersionConfig {
- EC_V1(
- /* protocolVersion= */ PROTOCOL_VERSION_EC_V1,
- /* aesCtrKeySize= */ 128 / 8,
- /* hmacSha256KeySize= */ 128 / 8,
- /* isEncryptionRequired= */ true,
- /* supportsIntermediateSigningKeys= */ false),
- EC_V2(
- /* protocolVersion= */ PROTOCOL_VERSION_EC_V2,
- /* aesCtrKeySize= */ 256 / 8,
- /* hmacSha256KeySize= */ 256 / 8,
- /* isEncryptionRequired= */ true,
- /* supportsIntermediateSigningKeys= */ true),
- EC_V2_SIGNING_ONLY(
- /* protocolVersion= */ PROTOCOL_VERSION_EC_V2_SIGNING_ONLY,
- /* aesCtrKeySize= */ 256 / 8,
- /* hmacSha256KeySize= */ 256 / 8,
- /* isEncryptionRequired= */ false,
- /* supportsIntermediateSigningKeys= */ true);
-
- public final String protocolVersion;
- public final int aesCtrKeySize;
- public final int hmacSha256KeySize;
- public final boolean isEncryptionRequired;
- public final boolean supportsIntermediateSigningKeys;
-
- ProtocolVersionConfig(
- String protocolVersion,
- int aesCtrKeySize,
- int hmacSha256KeySize,
- boolean isEncryptionRequired,
- boolean supportsIntermediateSigningKeys) {
- this.protocolVersion = protocolVersion;
- this.aesCtrKeySize = aesCtrKeySize;
- this.hmacSha256KeySize = hmacSha256KeySize;
- this.isEncryptionRequired = isEncryptionRequired;
- this.supportsIntermediateSigningKeys = supportsIntermediateSigningKeys;
- }
-
- public static ProtocolVersionConfig forProtocolVersion(String protocolVersion) {
- for (ProtocolVersionConfig protocolVersionConfig : ProtocolVersionConfig.values()) {
- if (protocolVersionConfig.protocolVersion.equals(protocolVersion)) {
- return protocolVersionConfig;
- }
- }
- throw new IllegalArgumentException("Unknown protocol version: " + protocolVersion);
- }
- }
-
- private PaymentMethodTokenConstants() {}
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java
deleted file mode 100644
index 6d2637a..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Hkdf;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.Arrays;
-
-/**
- * A {@link HybridDecrypt} implementation for the hybrid encryption used in <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- */
-class PaymentMethodTokenHybridDecrypt implements HybridDecrypt {
- private final PaymentMethodTokenRecipientKem recipientKem;
- private final ProtocolVersionConfig protocolVersionConfig;
-
- PaymentMethodTokenHybridDecrypt(
- final ECPrivateKey recipientPrivateKey, ProtocolVersionConfig protocolVersionConfig)
- throws GeneralSecurityException {
- this(
- new PaymentMethodTokenRecipientKem() {
- @Override
- public byte[] computeSharedSecret(final byte[] ephemeralPublicKey)
- throws GeneralSecurityException {
- ECPublicKey publicKey =
- EllipticCurves.getEcPublicKey(
- recipientPrivateKey.getParams(),
- PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
- ephemeralPublicKey);
- return EllipticCurves.computeSharedSecret(recipientPrivateKey, publicKey);
- }
- },
- protocolVersionConfig);
- }
-
- PaymentMethodTokenHybridDecrypt(
- final PaymentMethodTokenRecipientKem recipientKem,
- ProtocolVersionConfig protocolVersionConfig) {
- this.recipientKem = recipientKem;
- this.protocolVersionConfig = protocolVersionConfig;
- }
-
- @Override
- public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo)
- throws GeneralSecurityException {
- try {
- JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject();
- validate(json);
- byte[] demKey = kem(json, contextInfo);
- return dem(json, demKey);
- } catch (JsonParseException | IllegalStateException e) {
- throw new GeneralSecurityException("cannot decrypt; failed to parse JSON", e);
- }
- }
-
- private byte[] kem(JsonObject json, final byte[] contextInfo) throws GeneralSecurityException {
- int demKeySize = protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize;
- byte[] ephemeralPublicKey =
- Base64.decode(
- json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString());
- byte[] sharedSecret = recipientKem.computeSharedSecret(ephemeralPublicKey);
- return Hkdf.computeEciesHkdfSymmetricKey(
- ephemeralPublicKey,
- sharedSecret,
- PaymentMethodTokenConstants.HMAC_SHA256_ALGO,
- PaymentMethodTokenConstants.HKDF_EMPTY_SALT,
- contextInfo,
- demKeySize);
- }
-
- private byte[] dem(JsonObject json, final byte[] demKey) throws GeneralSecurityException {
- byte[] hmacSha256Key =
- Arrays.copyOfRange(demKey, protocolVersionConfig.aesCtrKeySize, demKey.length);
- byte[] encryptedMessage =
- Base64.decode(
- json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString());
- byte[] computedTag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, encryptedMessage);
- byte[] expectedTag =
- Base64.decode(json.get(PaymentMethodTokenConstants.JSON_TAG_KEY).getAsString());
- if (!Bytes.equal(expectedTag, computedTag)) {
- throw new GeneralSecurityException("cannot decrypt; invalid MAC");
- }
- byte[] aesCtrKey = Arrays.copyOf(demKey, protocolVersionConfig.aesCtrKeySize);
- return PaymentMethodTokenUtil.aesCtr(aesCtrKey, encryptedMessage);
- }
-
- private void validate(JsonObject payload) throws GeneralSecurityException {
- if (!payload.has(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY)
- || !payload.has(PaymentMethodTokenConstants.JSON_TAG_KEY)
- || !payload.has(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY)
- || payload.size() != 3) {
- throw new GeneralSecurityException(
- "The payload must contain exactly encryptedMessage, tag and ephemeralPublicKey");
- }
- }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java
deleted file mode 100644
index ff3a418..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EciesHkdfSenderKem;
-import com.google.gson.JsonObject;
-import com.google.gson.internal.Streams;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPublicKey;
-import java.util.Arrays;
-
-/**
- * A {@link HybridEncrypt} implementation for the hybrid encryption used in <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- */
-class PaymentMethodTokenHybridEncrypt implements HybridEncrypt {
- private final EciesHkdfSenderKem senderKem;
- private final ProtocolVersionConfig protocolVersionConfig;
-
- public PaymentMethodTokenHybridEncrypt(
- final ECPublicKey recipientPublicKey, final ProtocolVersionConfig protocolVersionConfig) {
- this.senderKem = new EciesHkdfSenderKem(recipientPublicKey);
- this.protocolVersionConfig = protocolVersionConfig;
- }
-
- static String jsonEncodeCiphertext(byte[] ciphertext, byte[] tag, byte[] ephemeralPublicKey)
- throws GeneralSecurityException {
- JsonObject result = new JsonObject();
- result.addProperty(
- PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(ciphertext));
- result.addProperty(
- PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY, Base64.encode(ephemeralPublicKey));
- result.addProperty(PaymentMethodTokenConstants.JSON_TAG_KEY, Base64.encode(tag));
- StringWriter stringWriter = new StringWriter();
- JsonWriter jsonWriter = new JsonWriter(stringWriter);
- jsonWriter.setHtmlSafe(true);
- try {
- Streams.write(result, jsonWriter);
- return stringWriter.toString();
- } catch (IOException e) {
- throw new GeneralSecurityException("cannot encrypt; JSON error", e);
- }
- }
-
- @Override
- public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo)
- throws GeneralSecurityException {
- int symmetricKeySize =
- protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize;
- EciesHkdfSenderKem.KemKey kemKey =
- senderKem.generateKey(
- PaymentMethodTokenConstants.HMAC_SHA256_ALGO,
- PaymentMethodTokenConstants.HKDF_EMPTY_SALT,
- contextInfo,
- symmetricKeySize,
- PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT);
- byte[] aesCtrKey = Arrays.copyOf(kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize);
- byte[] ciphertext = PaymentMethodTokenUtil.aesCtr(aesCtrKey, plaintext);
- byte[] hmacSha256Key =
- Arrays.copyOfRange(
- kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize, symmetricKeySize);
- byte[] tag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, ciphertext);
- byte[] ephemeralPublicKey = kemKey.getKemBytes();
-
- String jsonEncodedCiphertext = jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey);
- return jsonEncodedCiphertext.getBytes(UTF_8);
- }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipient.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipient.java
deleted file mode 100644
index 5f6cfe7..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipient.java
+++ /dev/null
@@ -1,629 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaVerifyJce;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.joda.time.Instant;
-
-/**
- * An implementation of the recipient side of <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- *
- * <h3>Warning</h3>
- *
- * <p>This implementation only supports versions {@code ECv1}, {@code ECv2} and {@code
- * ECv2SigningOnly}.
- *
- * <h3>Typical usage</h3>
- *
- * <pre>{@code
- * PaymentMethodTokenRecipient recipient = new PaymentMethodTokenRecipient.Builder()
- * .fetchSenderVerifyingKeysWith(
- * GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
- * .recipientId(recipientId)
- * // Multiple recipient private keys can be added to support graceful key rotations
- * .addRecipientPrivateKey(recipientPrivateKey1)
- * .addRecipientPrivateKey(recipientPrivateKey2)
- * .build();
- * String ciphertext = ...;
- * String plaintext = recipient.unseal(ciphertext);
- * }</pre>
- *
- * <h3>Custom decryption</h3>
- *
- * <p>Recipients that store private keys in HSM can provide implementations of {@link
- * PaymentMethodTokenRecipientKem} and configure Tink to use their custom code with {@link
- * PaymentMethodTokenRecipient.Builder#addRecipientKem}.
- *
- * <pre>{@code
- * PaymentMethodTokenRecipient recipient = new PaymentMethodTokenRecipient.Builder()
- * .fetchSenderVerifyingKeysWith(
- * GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
- * .recipientId(recipientId)
- * .addRecipientKem(new MyPaymentMethodTokenRecipientKem())
- * .build();
- * String ciphertext = ...;
- * String plaintext = recipient.unseal(ciphertext);
- * }</pre>
- *
- * @see <a href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment
- * Method Token standard</a>
- * @since 1.0.0
- */
-public final class PaymentMethodTokenRecipient {
- private final String protocolVersion;
- private final List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders;
- private final List<HybridDecrypt> hybridDecrypters = new ArrayList<>();
- private final String senderId;
- private final String recipientId;
-
- PaymentMethodTokenRecipient(
- String protocolVersion,
- List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders,
- String senderId,
- List<ECPrivateKey> recipientPrivateKeys,
- List<PaymentMethodTokenRecipientKem> recipientKems,
- String recipientId)
- throws GeneralSecurityException {
- if (!protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
- && !protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- && !protocolVersion.equals(
- PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)) {
- throw new IllegalArgumentException("invalid version: " + protocolVersion);
- }
- this.protocolVersion = protocolVersion;
- if (senderVerifyingKeysProviders == null || senderVerifyingKeysProviders.isEmpty()) {
- throw new IllegalArgumentException(
- "must set at least one way to get sender's verifying key using"
- + " Builder.fetchSenderVerifyingKeysWith or Builder.senderVerifyingKeys");
- }
- this.senderVerifyingKeysProviders = senderVerifyingKeysProviders;
- this.senderId = senderId;
-
- ProtocolVersionConfig protocolVersionConfig =
- ProtocolVersionConfig.forProtocolVersion(protocolVersion);
- if (protocolVersionConfig.isEncryptionRequired) {
- if (recipientPrivateKeys.isEmpty() && recipientKems.isEmpty()) {
- throw new IllegalArgumentException(
- "must add at least one recipient's decrypting key using Builder.addRecipientPrivateKey "
- + "or Builder.addRecipientKem");
- }
- for (ECPrivateKey privateKey : recipientPrivateKeys) {
- hybridDecrypters.add(
- new PaymentMethodTokenHybridDecrypt(privateKey, protocolVersionConfig));
- }
- for (PaymentMethodTokenRecipientKem kem : recipientKems) {
- hybridDecrypters.add(new PaymentMethodTokenHybridDecrypt(kem, protocolVersionConfig));
- }
- } else {
- if (!recipientPrivateKeys.isEmpty() || !recipientKems.isEmpty()) {
- throw new IllegalArgumentException(
- "must not set private decrypting key using Builder.addRecipientPrivateKey "
- + "or Builder.addRecipientDecrypter");
- }
- }
-
- if (recipientId == null) {
- throw new IllegalArgumentException("must set recipient Id using Builder.recipientId");
- }
- this.recipientId = recipientId;
- }
-
- private PaymentMethodTokenRecipient(Builder builder) throws GeneralSecurityException {
- this(
- builder.protocolVersion,
- builder.senderVerifyingKeysProviders,
- builder.senderId,
- builder.recipientPrivateKeys,
- builder.recipientKems,
- builder.recipientId);
- }
-
- /**
- * Builder for {@link PaymentMethodTokenRecipient}.
- *
- * @since 1.0.0
- */
- public static class Builder {
- private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1;
- private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID;
- private String recipientId = null;
- private final List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders =
- new ArrayList<SenderVerifyingKeysProvider>();
- private final List<ECPrivateKey> recipientPrivateKeys = new ArrayList<ECPrivateKey>();
- private final List<PaymentMethodTokenRecipientKem> recipientKems = new ArrayList<>();
-
- public Builder() {}
-
- /** Sets the protocolVersion. */
- public Builder protocolVersion(String val) {
- protocolVersion = val;
- return this;
- }
-
- /** Sets the sender Id. */
- public Builder senderId(String val) {
- senderId = val;
- return this;
- }
-
- /** Sets the recipient Id. */
- public Builder recipientId(String val) {
- recipientId = val;
- return this;
- }
-
- /**
- * Fetches verifying public keys of the sender using {@link GooglePaymentsPublicKeysManager}.
- *
- * <p>This is the preferred method of specifying the verifying public keys of the sender.
- */
- public Builder fetchSenderVerifyingKeysWith(
- final GooglePaymentsPublicKeysManager googlePaymentsPublicKeysManager)
- throws GeneralSecurityException {
- this.senderVerifyingKeysProviders.add(
- new SenderVerifyingKeysProvider() {
- @Override
- public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
- try {
- return parseTrustedSigningKeysJson(
- protocolVersion, googlePaymentsPublicKeysManager.getTrustedSigningKeysJson());
- } catch (IOException e) {
- throw new GeneralSecurityException("Failed to fetch keys!", e);
- }
- }
- });
- return this;
- }
-
- /**
- * Sets the trusted verifying public keys of the sender.
- *
- * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
- * sender, prefer calling {@link #fetchSenderVerifyingKeysWith} passing it an instance of {@link
- * GooglePaymentsPublicKeysManager}. It will take care of fetching fresh keys and caching in
- * memory. Only use this method if you can't use {@link #fetchSenderVerifyingKeysWith} and be
- * aware you will need to handle Google key rotations yourself.
- *
- * <p>The given string is a JSON object formatted like the following:
- *
- * <pre>
- * {
- * "keys": [
- * {
- * "keyValue": "encoded public key",
- * "protocolVersion": "ECv1"
- * },
- * {
- * "keyValue": "encoded public key",
- * "protocolVersion": "ECv1"
- * },
- * ],
- * }
- * </pre>
- *
- * <p>Each public key will be a base64 (no wrapping, padded) version of the key encoded in ASN.1
- * type SubjectPublicKeyInfo defined in the X.509 standard.
- */
- public Builder senderVerifyingKeys(final String trustedSigningKeysJson)
- throws GeneralSecurityException {
- this.senderVerifyingKeysProviders.add(
- new SenderVerifyingKeysProvider() {
- @Override
- public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
- return parseTrustedSigningKeysJson(protocolVersion, trustedSigningKeysJson);
- }
- });
- return this;
- }
-
- /**
- * Adds a verifying public key of the sender.
- *
- * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
- * sender, prefer calling {@link #fetchSenderVerifyingKeysWith} passing it an instance of {@link
- * GooglePaymentsPublicKeysManager}. It will take care of fetching fresh keys and caching in
- * memory. Only use this method if you can't use {@link #fetchSenderVerifyingKeysWith} and be
- * aware you will need to handle Google key rotations yourself.
- *
- * <p>The public key is a base64 (no wrapping, padded) version of the key encoded in ASN.1 type
- * SubjectPublicKeyInfo defined in the X.509 standard.
- *
- * <p>Multiple keys may be added. This utility will then verify any message signed with any of
- * the private keys corresponding to the public keys added. Adding multiple keys is useful for
- * handling key rotation.
- */
- public Builder addSenderVerifyingKey(final String val) throws GeneralSecurityException {
- this.senderVerifyingKeysProviders.add(
- new SenderVerifyingKeysProvider() {
- @Override
- public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
- return Collections.singletonList(PaymentMethodTokenUtil.x509EcPublicKey(val));
- }
- });
- return this;
- }
-
- /**
- * Adds a verifying public key of the sender.
- *
- * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
- * sender, prefer calling {@link #fetchSenderVerifyingKeysWith} passing it an instance of {@link
- * GooglePaymentsPublicKeysManager}. It will take care of fetching fresh keys and caching in
- * memory. Only use this method if you can't use {@link #fetchSenderVerifyingKeysWith} and be
- * aware you will need to handle Google key rotations yourself.
- */
- public Builder addSenderVerifyingKey(final ECPublicKey val) throws GeneralSecurityException {
- this.senderVerifyingKeysProviders.add(
- new SenderVerifyingKeysProvider() {
- @Override
- public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
- return Collections.singletonList(val);
- }
- });
- return this;
- }
-
- /**
- * Adds the decryption private key of the recipient.
- *
- * <p>It must be base64 encoded PKCS8 private key.
- */
- public Builder addRecipientPrivateKey(String val) throws GeneralSecurityException {
- return addRecipientPrivateKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
- }
-
- /** Adds the decryption private key of the recipient. */
- public Builder addRecipientPrivateKey(ECPrivateKey val) throws GeneralSecurityException {
- recipientPrivateKeys.add(val);
- return this;
- }
-
- /**
- * Adds a custom {@link PaymentMethodTokenRecipientKem}.
- *
- * <p>This is useful for clients that store keys in an HSM and need a more control on how the
- * key is used. If you are not using an HSM, you probably should just use {@link
- * #addRecipientPrivateKey}.
- *
- * @since 1.1.0
- */
- public Builder addRecipientKem(PaymentMethodTokenRecipientKem kem) {
- recipientKems.add(kem);
- return this;
- }
-
- public PaymentMethodTokenRecipient build() throws GeneralSecurityException {
- return new PaymentMethodTokenRecipient(this);
- }
- }
-
- /**
- * Unseal the given {@code sealedMessage} by performing the necessary signature verification and
- * decryption (if required) steps based on the protocolVersion.
- */
- public String unseal(final String sealedMessage) throws GeneralSecurityException {
- try {
- if (protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)) {
- return unsealECV1(sealedMessage);
- } else if (protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)) {
- return unsealECV2(sealedMessage);
- } else if (protocolVersion.equals(
- PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)) {
- return unsealECV2SigningOnly(sealedMessage);
- }
- throw new IllegalArgumentException("unsupported version: " + protocolVersion);
- } catch (JsonParseException | IllegalStateException e) {
- throw new GeneralSecurityException("cannot unseal; invalid JSON message", e);
- }
- }
-
- private String unsealECV1(String sealedMessage) throws GeneralSecurityException {
- JsonObject jsonMsg = JsonParser.parseString(sealedMessage).getAsJsonObject();
- validateECV1(jsonMsg);
- String signedMessage = verifyECV1(jsonMsg);
- String decryptedMessage = decrypt(signedMessage);
- validateMessage(decryptedMessage);
- return decryptedMessage;
- }
-
- private String unsealECV2(String sealedMessage) throws GeneralSecurityException {
- JsonObject jsonMsg = JsonParser.parseString(sealedMessage).getAsJsonObject();
- validateECV2(jsonMsg);
- String signedMessage = verifyECV2(jsonMsg);
- String decryptedMessage = decrypt(signedMessage);
- validateMessage(decryptedMessage);
- return decryptedMessage;
- }
-
- private String unsealECV2SigningOnly(String sealedMessage) throws GeneralSecurityException {
- JsonObject jsonMsg = JsonParser.parseString(sealedMessage).getAsJsonObject();
- validateECV2(jsonMsg);
- String message = verifyECV2(jsonMsg);
- validateMessage(message);
- return message;
- }
-
- private String verifyECV1(final JsonObject jsonMsg) throws GeneralSecurityException {
- byte[] signature =
- Base64.decode(jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY).getAsString());
- String signedMessage =
- jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString();
- byte[] signedBytes = getSignedBytes(protocolVersion, signedMessage);
- verify(
- protocolVersion,
- senderVerifyingKeysProviders,
- Collections.singletonList(signature),
- signedBytes);
- return signedMessage;
- }
-
- private String verifyECV2(final JsonObject jsonMsg) throws GeneralSecurityException {
- byte[] signature =
- Base64.decode(jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY).getAsString());
- String signedMessage =
- jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString();
- byte[] signedBytes = getSignedBytes(protocolVersion, signedMessage);
- verify(
- protocolVersion,
- verifyIntermediateSigningKey(jsonMsg),
- Collections.singletonList(signature),
- signedBytes);
- return signedMessage;
- }
-
- private byte[] getSignedBytes(String protocolVersion, String signedMessage)
- throws GeneralSecurityException {
- return PaymentMethodTokenUtil.toLengthValue(
- // The order of the parameters matters.
- senderId, recipientId, protocolVersion, signedMessage);
- }
-
- private void validateMessage(String decryptedMessage) throws GeneralSecurityException {
- JsonObject decodedMessage;
-
- try {
- decodedMessage = JsonParser.parseString(decryptedMessage).getAsJsonObject();
- } catch (JsonParseException | IllegalStateException e) {
- // Message wasn't a valid JSON, so nothing to validate.
- return;
- }
-
- // If message expiration is present, checking it.
- if (decodedMessage.has(PaymentMethodTokenConstants.JSON_MESSAGE_EXPIRATION_KEY)) {
- long expirationInMillis =
- Long.parseLong(
- decodedMessage
- .get(PaymentMethodTokenConstants.JSON_MESSAGE_EXPIRATION_KEY)
- .getAsString());
- if (expirationInMillis <= Instant.now().getMillis()) {
- throw new GeneralSecurityException("expired payload");
- }
- }
- }
-
- private static void verify(
- final String protocolVersion,
- final List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders,
- final List<byte[]> signatures,
- final byte[] signedBytes)
- throws GeneralSecurityException {
- boolean verified = false;
- for (SenderVerifyingKeysProvider verifyingKeysProvider : senderVerifyingKeysProviders) {
- for (ECPublicKey publicKey : verifyingKeysProvider.get(protocolVersion)) {
- EcdsaVerifyJce verifier =
- new EcdsaVerifyJce(
- publicKey, PaymentMethodTokenConstants.ECDSA_HASH_SHA256, EcdsaEncoding.DER);
- for (byte[] signature : signatures) {
- try {
- verifier.verify(signature, signedBytes);
- // No exception means the signature is valid.
- verified = true;
- } catch (GeneralSecurityException e) {
- // ignored, try again
- }
- }
- }
- }
- if (!verified) {
- throw new GeneralSecurityException("cannot verify signature");
- }
- }
-
- private String decrypt(String ciphertext) throws GeneralSecurityException {
- for (HybridDecrypt hybridDecrypter : hybridDecrypters) {
- try {
- byte[] cleartext =
- hybridDecrypter.decrypt(
- ciphertext.getBytes(UTF_8), PaymentMethodTokenConstants.GOOGLE_CONTEXT_INFO_ECV1);
- return new String(cleartext, UTF_8);
- } catch (GeneralSecurityException e) {
- // ignored, try again
- }
- }
- throw new GeneralSecurityException("cannot decrypt");
- }
-
- private void validateECV1(final JsonObject jsonMsg) throws GeneralSecurityException {
- if (!jsonMsg.has(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY)
- || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY)
- || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY)
- || jsonMsg.size() != 3) {
- throw new GeneralSecurityException(
- "ECv1 message must contain exactly protocolVersion, signature and signedMessage");
- }
- String version =
- jsonMsg.get(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY).getAsString();
- if (!version.equals(protocolVersion)) {
- throw new GeneralSecurityException("invalid version: " + version);
- }
- }
-
- private void validateECV2(final JsonObject jsonMsg) throws GeneralSecurityException {
- if (!jsonMsg.has(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY)
- || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY)
- || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY)
- || !jsonMsg.has(PaymentMethodTokenConstants.JSON_INTERMEDIATE_SIGNING_KEY)
- || jsonMsg.size() != 4) {
- throw new GeneralSecurityException(
- protocolVersion
- + " message must contain exactly protocolVersion, intermediateSigningKey, "
- + "signature and signedMessage");
- }
- String version =
- jsonMsg.get(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY).getAsString();
- if (!version.equals(protocolVersion)) {
- throw new GeneralSecurityException("invalid version: " + version);
- }
- }
-
- /**
- * Verifies the intermediate key and returns a singleton list containing of {@code
- * SenderVerifyingKeysProvider} that provides the verified key.
- */
- private List<SenderVerifyingKeysProvider> verifyIntermediateSigningKey(JsonObject jsonMsg)
- throws GeneralSecurityException {
- JsonObject intermediateSigningKey =
- jsonMsg.get(PaymentMethodTokenConstants.JSON_INTERMEDIATE_SIGNING_KEY).getAsJsonObject();
- validateIntermediateSigningKey(intermediateSigningKey);
- ArrayList<byte[]> signatures = new ArrayList<>();
- JsonArray signaturesJson =
- intermediateSigningKey
- .get(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY)
- .getAsJsonArray();
- for (int i = 0; i < signaturesJson.size(); i++) {
- signatures.add(Base64.decode(signaturesJson.get(i).getAsString()));
- }
- String signedKeyAsString =
- intermediateSigningKey.get(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY).getAsString();
- byte[] signedBytes =
- PaymentMethodTokenUtil.toLengthValue(
- // The order of the parameters matters.
- senderId, protocolVersion, signedKeyAsString);
- verify(protocolVersion, senderVerifyingKeysProviders, signatures, signedBytes);
- JsonObject signedKey = JsonParser.parseString(signedKeyAsString).getAsJsonObject();
- validateSignedKey(signedKey);
- final String key = signedKey.get(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY).getAsString();
- SenderVerifyingKeysProvider provider =
- new SenderVerifyingKeysProvider() {
- @Override
- public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
- if (PaymentMethodTokenRecipient.this.protocolVersion.equals(protocolVersion)) {
- return Collections.singletonList(PaymentMethodTokenUtil.x509EcPublicKey(key));
- } else {
- return Collections.emptyList();
- }
- }
- };
- return Collections.singletonList(provider);
- }
-
- private JsonObject validateIntermediateSigningKey(final JsonObject intermediateSigningKey)
- throws GeneralSecurityException {
- if (!intermediateSigningKey.has(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY)
- || !intermediateSigningKey.has(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY)
- || intermediateSigningKey.size() != 2) {
- throw new GeneralSecurityException(
- "intermediateSigningKey must contain exactly signedKey and signatures");
- }
- return intermediateSigningKey;
- }
-
- private void validateSignedKey(final JsonObject signedKey) throws GeneralSecurityException {
- // Note: allowing further keys to be added so we can extend the protocol if needed in the future
- if (!signedKey.has(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY)
- || !signedKey.has(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY)) {
- throw new GeneralSecurityException(
- "intermediateSigningKey.signedKey must contain keyValue and keyExpiration");
- }
-
- // If message expiration is present, checking it.
- long expirationInMillis =
- Long.parseLong(
- signedKey.get(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY).getAsString());
- if (expirationInMillis <= Instant.now().getMillis()) {
- throw new GeneralSecurityException("expired intermediateSigningKey");
- }
- }
-
- private static List<ECPublicKey> parseTrustedSigningKeysJson(
- String protocolVersion, String trustedSigningKeysJson) throws GeneralSecurityException {
- List<ECPublicKey> senderVerifyingKeys = new ArrayList<>();
- try {
- JsonArray keys =
- JsonParser.parseString(trustedSigningKeysJson)
- .getAsJsonObject()
- .get("keys")
- .getAsJsonArray();
- for (int i = 0; i < keys.size(); i++) {
- JsonObject key = keys.get(i).getAsJsonObject();
- if (protocolVersion.equals(
- key.get(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY).getAsString())) {
-
- if (key.has(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY)) {
- // If message expiration is present, checking it.
- long expirationInMillis =
- Long.parseLong(
- key.get(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY).getAsString());
- if (expirationInMillis <= Instant.now().getMillis()) {
- // Ignore expired keys
- continue;
- }
- } else if (!protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)) {
- // keyExpiration is required in all versions except ECv1, so if it is missing we should
- // skip using this key.
- // In ECv1 the expiration is optional because it is assumed that the caller is
- // respecting the HTTP cache headers and not using the trustedSigningKeysJson that are
- // expired according to the headers.
- continue;
- }
-
- senderVerifyingKeys.add(
- PaymentMethodTokenUtil.x509EcPublicKey(key.get("keyValue").getAsString()));
- }
- }
- } catch (JsonParseException | IllegalStateException e) {
- throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
- }
- if (senderVerifyingKeys.isEmpty()) {
- throw new GeneralSecurityException("no trusted keys are available for this protocol version");
- }
- return senderVerifyingKeys;
- }
-
- private interface SenderVerifyingKeysProvider {
- List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException;
- }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java
deleted file mode 100644
index 47d322b..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import java.security.GeneralSecurityException;
-
-/**
- * Interface for recipient's key encapsulation mechanism (KEM).
- *
- * <p>Google Pay's tokens are encrypted using ECIES which is a hybrid encryption mode consisting of
- * two steps: key encapsulation mechanisam (KEM) using Elliptic Curve Diffie Hellman (ECDH) and HKDF
- * and data encapsulation mechanism (DEM) using AES-CTR-HMAC.
- *
- * <p>During encryption, the KEM step takes the recipient's public key and produces a DEM key and an
- * ephemeral public key. The DEM key is then used to encrypt the credit card data, and the ephemeral
- * public key is sent as the <b>ephemeralPublicKey</b> field of the payload.
- *
- * <p>To decrypt, the recipient must use their private key to compute an ECDH shared secret from the
- * ephemeral public key, and from that derive the DEM key using HKDF. If the recipient keeps the
- * private key in a HSM, they cannot load the private key in Tink, but they can implement this
- * interface and configure Tink to use their custom KEM implementation with {@link
- * PaymentMethodTokenRecipient.Builder#addRecipientKem}.
- *
- * @see <a href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment
- * Method Token standard</a>
- * @since 1.1.0
- */
-public interface PaymentMethodTokenRecipientKem {
- /**
- * Computes a shared secret from the {@code ephemeralPublicKey}, using ECDH.
- *
- * <p>{@code ephemeralPublicKey} is a point on the elliptic curve defined in the <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token standard</a>, encoded in uncompressed point format. In version ECv1 and ECv2 of the
- * standard, the elliptic curve is NIST P-256.
- *
- * <p>Note that you only needs to compute the shared secret, but you don't have to derive the DEM
- * key with HKDF -- that process is handled by Tink.
- */
- byte[] computeSharedSecret(final byte[] ephemeralPublicKey) throws GeneralSecurityException;
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java
deleted file mode 100644
index 2713854..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPublicKey;
-
-/**
- * A util that generates key pairs for the recipient side of <a
- * href="https://developers.google.com/android-pay/integration/payment-token-cryptography">Google
- * Payment Method Token</a>.
- *
- * <h3>Usage</h3>
- *
- * <pre>
- * bazel build apps/paymentmethodtoken/...
- * ./bazel-bin/apps/paymentmethodtoken/recipientkeygen
- * </pre>
- *
- * <p>Running that command will generate a fresh key pair. The private/public key can be found in
- * private_key.bin/public_key.bin. The content of private_key.bin can be passed to {@link
- * PaymentMethodTokenRecipient.Builder#addRecipientPrivateKey} and the content of public_key.bin can
- * be passed to {@link PaymentMethodTokenSender.Builder#rawUncompressedRecipientPublicKey}.
- */
-public final class PaymentMethodTokenRecipientKeyGen {
- private static final String PRIVATE_KEY_FILE = "private_key.bin";
-
- private static final String PUBLIC_KEY_FILE = "public_key.bin";
-
- private static void generateKey() throws GeneralSecurityException, IOException {
- KeyPair keyPair = EllipticCurves.generateKeyPair(PaymentMethodTokenConstants.P256_CURVE_TYPE);
- writeBase64(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
-
- ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
- writeBase64(
- PUBLIC_KEY_FILE,
- EllipticCurves.pointEncode(
- PaymentMethodTokenConstants.P256_CURVE_TYPE,
- PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
- publicKey.getW()));
- }
-
- private static void writeBase64(String pathname, byte[] content) throws IOException {
- File out = new File(pathname);
- if (out.exists()) {
- System.out.println("Please make sure that " + pathname + " does not exist.");
- System.exit(-1);
- }
- FileOutputStream stream = new FileOutputStream(out);
- stream.write(Base64.encode(content, Base64.DEFAULT | Base64.NO_WRAP));
- stream.close();
- }
-
- public static void main(String[] args) throws GeneralSecurityException, IOException {
- System.out.println("Generating key....");
- generateKey();
- System.out.println("done.");
- }
-
- private PaymentMethodTokenRecipientKeyGen() {}
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSender.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSender.java
deleted file mode 100644
index 25d0fe2..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSender.java
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.PublicKeySign;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaSignJce;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import com.google.gson.internal.Streams;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-
-/**
- * An implementation of the sender side of <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- *
- * <h3>Warning</h3>
- *
- * <p>This implementation supports only versions {@code ECv1} and {@code ECv2}.
- *
- * <h3>Usage</h3>
- *
- * <pre>{@code
- * PaymentMethodTokenSender sender = new PaymentMethodTokenSender.Builder()
- * .senderId(senderId)
- * .senderSigningKey(senderPrivateKey)
- * .recipientId(recipientId)
- * .recipientPublicKey(recipientPublicKey)
- * .build();
- * String plaintext = "blah";
- * String ciphertext = sender.seal(plaintext);
- * }</pre>
- *
- * @see <a href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment
- * Method Token standard</a>
- * @since 1.0.0
- */
-public final class PaymentMethodTokenSender {
- private final String protocolVersion;
- private final ProtocolVersionConfig protocolVersionConfig;
- private final PublicKeySign signer;
- private final String senderIntermediateCert;
- private final String senderId;
- private final String recipientId;
-
- private HybridEncrypt hybridEncrypter;
-
- PaymentMethodTokenSender(Builder builder) throws GeneralSecurityException {
- switch (builder.protocolVersion) {
- case PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1:
- validateV1(builder);
- break;
- case PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2:
- validateV2(builder);
- break;
- case PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY:
- validateV2SigningOnly(builder);
- break;
- default:
- throw new IllegalArgumentException("invalid version: " + builder.protocolVersion);
- }
-
- this.protocolVersion = builder.protocolVersion;
- this.protocolVersionConfig = ProtocolVersionConfig.forProtocolVersion(protocolVersion);
- this.signer =
- new EcdsaSignJce(
- builder.senderIntermediateSigningKey != null
- ? builder.senderIntermediateSigningKey
- : builder.senderSigningKey,
- PaymentMethodTokenConstants.ECDSA_HASH_SHA256,
- EcdsaEncoding.DER);
- this.senderId = builder.senderId;
- if (protocolVersionConfig.isEncryptionRequired) {
- this.hybridEncrypter =
- new PaymentMethodTokenHybridEncrypt(builder.recipientPublicKey, protocolVersionConfig);
- }
- if (builder.recipientId == null) {
- throw new IllegalArgumentException("must set recipient Id using Builder.recipientId");
- }
- this.recipientId = builder.recipientId;
- this.senderIntermediateCert = builder.senderIntermediateCert;
- }
-
- /**
- * Builder for {@link PaymentMethodTokenSender}.
- *
- * @since 1.0.0
- */
- public static class Builder {
- private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1;
- private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID;
- private String recipientId = null;
- private ECPrivateKey senderSigningKey = null;
- private ECPrivateKey senderIntermediateSigningKey = null;
- private String senderIntermediateCert = null;
- private ECPublicKey recipientPublicKey = null;
-
- public Builder() {}
-
- /** Sets the protocolVersion. */
- public Builder protocolVersion(String val) {
- protocolVersion = val;
- return this;
- }
-
- /** Sets the sender Id. */
- public Builder senderId(String val) {
- senderId = val;
- return this;
- }
-
- /** Sets the recipient Id. */
- public Builder recipientId(String val) {
- recipientId = val;
- return this;
- }
-
- /**
- * Sets the signing key of the sender.
- *
- * <p>It must be base64 encoded PKCS8 private key.
- */
- public Builder senderSigningKey(String val) throws GeneralSecurityException {
- senderSigningKey = PaymentMethodTokenUtil.pkcs8EcPrivateKey(val);
- return this;
- }
-
- public Builder senderSigningKey(ECPrivateKey val) throws GeneralSecurityException {
- senderSigningKey = val;
- return this;
- }
-
- /**
- * Sets the intermediate signing key of the sender.
- *
- * <p>It must be base64 encoded PKCS8 private key.
- *
- * @since 1.1.0
- */
- public Builder senderIntermediateSigningKey(String val) throws GeneralSecurityException {
- return senderIntermediateSigningKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
- }
-
- /**
- * Sets the intermediate signing key of the sender.
- *
- * @since 1.1.0
- */
- public Builder senderIntermediateSigningKey(ECPrivateKey val) throws GeneralSecurityException {
- senderIntermediateSigningKey = val;
- return this;
- }
-
- /**
- * JSON containing sender intermediate signing key and a signature of it by the sender signing
- * key.
- *
- * <p>This can be generated by {@link SenderIntermediateCertFactory}.
- *
- * @since 1.1.0
- */
- public Builder senderIntermediateCert(String val) throws GeneralSecurityException {
- this.senderIntermediateCert = val;
- return this;
- }
-
- /**
- * Sets the encryption public key of the recipient.
- *
- * <p>The public key is a base64 (no wrapping, padded) version of the key encoded in ASN.1 type
- * SubjectPublicKeyInfo defined in the X.509 standard.
- */
- public Builder recipientPublicKey(String val) throws GeneralSecurityException {
- recipientPublicKey = PaymentMethodTokenUtil.x509EcPublicKey(val);
- return this;
- }
-
- public Builder recipientPublicKey(ECPublicKey val) throws GeneralSecurityException {
- recipientPublicKey = val;
- return this;
- }
-
- /**
- * Sets the encryption public key of the recipient.
- *
- * <p>The public key must be formatted as base64 encoded uncompressed point format. This format
- * is described in more detail in "Public Key Cryptography For The Financial Services Industry:
- * The Elliptic Curve Digital Signature Algorithm (ECDSA)", ANSI X9.62, 1998
- */
- public Builder rawUncompressedRecipientPublicKey(String val) throws GeneralSecurityException {
- recipientPublicKey = PaymentMethodTokenUtil.rawUncompressedEcPublicKey(val);
- return this;
- }
-
- public PaymentMethodTokenSender build() throws GeneralSecurityException {
- return new PaymentMethodTokenSender(this);
- }
- }
-
- /** Seals the input message according to the Payment Method Token specification. */
- public String seal(final String message) throws GeneralSecurityException {
- if (protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
- || protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- || protocolVersion.equals(
- PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)) {
- return sealV1OrV2(message);
- }
- throw new GeneralSecurityException("Unsupported version: " + protocolVersion);
- }
-
- private String sealV1OrV2(final String message) throws GeneralSecurityException {
- String signedMessage =
- protocolVersionConfig.isEncryptionRequired
- ? new String(
- hybridEncrypter.encrypt(
- message.getBytes(UTF_8), PaymentMethodTokenConstants.GOOGLE_CONTEXT_INFO_ECV1),
- UTF_8)
- : message;
- return signV1OrV2(signedMessage);
- }
-
- static String jsonEncodeSignedMessage(
- String message, String protocolVersion, byte[] signature, String senderIntermediateCert)
- throws GeneralSecurityException {
- try {
- JsonObject result = new JsonObject();
- result.addProperty(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY, Base64.encode(signature));
- if (senderIntermediateCert != null) {
- result.add(
- PaymentMethodTokenConstants.JSON_INTERMEDIATE_SIGNING_KEY,
- JsonParser.parseString(senderIntermediateCert).getAsJsonObject());
- }
- result.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, protocolVersion);
- result.addProperty(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY, message);
- StringWriter stringWriter = new StringWriter();
- JsonWriter jsonWriter = new JsonWriter(stringWriter);
- jsonWriter.setHtmlSafe(true);
- Streams.write(result, jsonWriter);
- return stringWriter.toString();
- } catch (JsonParseException | IllegalStateException | IOException e) {
- throw new GeneralSecurityException("cannot seal; JSON error", e);
- }
- }
-
- private String signV1OrV2(String message) throws GeneralSecurityException {
- byte[] toSignBytes =
- PaymentMethodTokenUtil.toLengthValue(
- // The order of the parameters matters.
- senderId, recipientId, protocolVersion, message);
- byte[] signature = signer.sign(toSignBytes);
- return jsonEncodeSignedMessage(message, protocolVersion, signature, senderIntermediateCert);
- }
-
- private static void validateV1(Builder builder) {
- // ECv1 signed payloads directly.
- if (builder.senderSigningKey == null) {
- throw new IllegalArgumentException(
- "must set sender's signing key using Builder.senderSigningKey");
- }
- if (builder.senderIntermediateSigningKey != null) {
- throw new IllegalArgumentException(
- "must not set sender's intermediate signing key using "
- + "Builder.senderIntermediateSigningKey");
- }
- if (builder.senderIntermediateCert != null) {
- throw new IllegalArgumentException(
- "must not set signed sender's intermediate signing key using "
- + "Builder.senderIntermediateCert");
- }
- if (builder.recipientPublicKey == null) {
- throw new IllegalArgumentException(
- "must set recipient's public key using Builder.recipientPublicKey");
- }
- }
-
- private static void validateV2(Builder builder) {
- validateIntermediateSigningKeys(builder);
- if (builder.recipientPublicKey == null) {
- throw new IllegalArgumentException(
- "must set recipient's public key using Builder.recipientPublicKey");
- }
- }
-
- private static void validateV2SigningOnly(Builder builder) {
- validateIntermediateSigningKeys(builder);
- if (builder.recipientPublicKey != null) {
- throw new IllegalArgumentException(
- "must not set recipient's public key using Builder.recipientPublicKey");
- }
- }
-
- private static void validateIntermediateSigningKeys(Builder builder) {
- // ECv2 and newer protocols use an intermediate signing key.
- if (builder.senderSigningKey != null) {
- throw new IllegalArgumentException(
- "must not set sender's signing key using Builder.senderSigningKey");
- }
- if (builder.senderIntermediateSigningKey == null) {
- throw new IllegalArgumentException(
- "must set sender's intermediate signing key using "
- + "Builder.senderIntermediateSigningKey");
- }
- if (builder.senderIntermediateCert == null) {
- throw new IllegalArgumentException(
- "must set signed sender's intermediate signing key using "
- + "Builder.senderIntermediateCert");
- }
- }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java
deleted file mode 100644
index 185038d..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EngineFactory;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/** Various helpers. */
-final class PaymentMethodTokenUtil {
- static ECPublicKey rawUncompressedEcPublicKey(String rawUncompressedPublicKey)
- throws GeneralSecurityException {
- return EllipticCurves.getEcPublicKey(
- PaymentMethodTokenConstants.P256_CURVE_TYPE,
- PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
- Base64.decode(rawUncompressedPublicKey));
- }
-
- static ECPublicKey x509EcPublicKey(String x509PublicKey) throws GeneralSecurityException {
- return EllipticCurves.getEcPublicKey(Base64.decode(x509PublicKey));
- }
-
- static ECPrivateKey pkcs8EcPrivateKey(String pkcs8PrivateKey) throws GeneralSecurityException {
- return EllipticCurves.getEcPrivateKey(Base64.decode(pkcs8PrivateKey));
- }
-
- static byte[] toLengthValue(String... chunks) throws GeneralSecurityException {
- byte[] out = new byte[0];
- for (String chunk : chunks) {
- byte[] bytes = chunk.getBytes(StandardCharsets.UTF_8);
- out = Bytes.concat(out, Bytes.intToByteArray(4, bytes.length));
- out = Bytes.concat(out, bytes);
- }
- return out;
- }
-
- static byte[] aesCtr(final byte[] encryptionKey, final byte[] message)
- throws GeneralSecurityException {
- Cipher cipher = EngineFactory.CIPHER.getInstance(PaymentMethodTokenConstants.AES_CTR_ALGO);
- cipher.init(
- Cipher.ENCRYPT_MODE,
- new SecretKeySpec(encryptionKey, "AES"),
- new IvParameterSpec(PaymentMethodTokenConstants.AES_CTR_ZERO_IV));
- return cipher.doFinal(message);
- }
-
- static byte[] hmacSha256(final byte[] macKey, final byte[] encryptedMessage)
- throws GeneralSecurityException {
- SecretKeySpec key = new SecretKeySpec(macKey, PaymentMethodTokenConstants.HMAC_SHA256_ALGO);
- Mac mac = EngineFactory.MAC.getInstance(PaymentMethodTokenConstants.HMAC_SHA256_ALGO);
- mac.init(key);
- return mac.doFinal(encryptedMessage);
- }
-
- private PaymentMethodTokenUtil() {}
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java
deleted file mode 100644
index 0e3bfc7..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.PublicKeySign;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaSignJce;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.internal.Streams;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Creates a signed certificate with the intermediate signing keys used by the sender in certain
- * protocol versions.
- *
- * @since 1.1.0
- */
-public class SenderIntermediateCertFactory {
- private final List<PublicKeySign> signers;
- private final String intermediateSigningKey;
- private final String protocolVersion;
- private final String senderId;
- private final long expiration;
-
- private SenderIntermediateCertFactory(
- String protocolVersion,
- String senderId,
- List<ECPrivateKey> senderSigningKeys,
- String intermediateSigningKey,
- long expiration)
- throws GeneralSecurityException {
- if (!ProtocolVersionConfig.forProtocolVersion(protocolVersion)
- .supportsIntermediateSigningKeys) {
- throw new IllegalArgumentException("invalid version: " + protocolVersion);
- }
- if (senderSigningKeys.isEmpty()) {
- throw new IllegalArgumentException(
- "must add at least one sender's signing key using Builder.addSenderSigningKey");
- }
- if (expiration == 0) {
- throw new IllegalArgumentException("must set expiration using Builder.expiration");
- }
- if (expiration < 0) {
- throw new IllegalArgumentException("invalid negative expiration");
- }
- this.protocolVersion = protocolVersion;
- this.senderId = senderId;
- this.signers = new ArrayList<>();
- for (ECPrivateKey senderSigningKey : senderSigningKeys) {
- this.signers.add(
- new EcdsaSignJce(
- senderSigningKey, PaymentMethodTokenConstants.ECDSA_HASH_SHA256, EcdsaEncoding.DER));
- }
- this.intermediateSigningKey = intermediateSigningKey;
- this.expiration = expiration;
- }
-
- /**
- * Builder for {@link SenderIntermediateCertFactory}.
- *
- * @since 1.1.0
- */
- public static class Builder {
- private final List<ECPrivateKey> senderSigningKeys = new ArrayList<>();
- private String intermediateSigningKey;
- private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2;
- private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID;
- private long expiration;
-
- public Builder() {}
-
- /** Sets the protocolVersion. */
- public Builder protocolVersion(String val) {
- protocolVersion = val;
- return this;
- }
-
- /** Sets the sender Id. */
- public Builder senderId(String val) {
- senderId = val;
- return this;
- }
-
- /** Sets the expiration in millis since epoch. */
- public Builder expiration(long millisSinceEpoch) {
- expiration = millisSinceEpoch;
- return this;
- }
-
- /**
- * Adds a signing key of the sender.
- *
- * <p>It must be base64 encoded PKCS8 private key.
- */
- public Builder addSenderSigningKey(String val) throws GeneralSecurityException {
- return addSenderSigningKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
- }
-
- /**
- * Adds a signing key of the sender.
- *
- * <p>It must be base64 encoded PKCS8 private key.
- */
- public Builder addSenderSigningKey(ECPrivateKey val) throws GeneralSecurityException {
- this.senderSigningKeys.add(val);
- return this;
- }
-
- /**
- * Sets the intermediate signing key being signed.
- *
- * <p>The public key specified here is a base64 (no wrapping, padded) version of the key encoded
- * in ASN.1 type SubjectPublicKeyInfo defined in the X.509 standard.
- */
- public Builder senderIntermediateSigningKey(String val) throws GeneralSecurityException {
- // Parsing to validate the format
- PaymentMethodTokenUtil.x509EcPublicKey(val);
- intermediateSigningKey = val;
- return this;
- }
-
- public SenderIntermediateCertFactory build() throws GeneralSecurityException {
- return new SenderIntermediateCertFactory(
- protocolVersion, senderId, senderSigningKeys, intermediateSigningKey, expiration);
- }
- }
-
- static String jsonEncodeSignedKey(String intermediateSigningKey, long expiration) {
- try {
- JsonObject jsonObj = new JsonObject();
- jsonObj.addProperty(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY, intermediateSigningKey);
- jsonObj.addProperty(
- PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY, Long.toString(expiration));
- StringWriter stringWriter = new StringWriter();
- JsonWriter jsonWriter = new JsonWriter(stringWriter);
- jsonWriter.setHtmlSafe(true);
- Streams.write(jsonObj, jsonWriter);
- return stringWriter.toString();
- } catch (JsonParseException | IllegalStateException | IOException e) {
- throw new AssertionError("Failed to perform JSON encoding", e);
- }
- }
-
- static String jsonEncodeCertificate(String signedKey, ArrayList<String> signatures) {
- try {
- JsonArray jsonSignatures = new JsonArray();
- for (String signature : signatures) {
- jsonSignatures.add(signature);
- }
- JsonObject result = new JsonObject();
- result.addProperty(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY, signedKey);
- result.add(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY, jsonSignatures);
- StringWriter stringWriter = new StringWriter();
- JsonWriter jsonWriter = new JsonWriter(stringWriter);
- jsonWriter.setHtmlSafe(true);
- Streams.write(result, jsonWriter);
- return stringWriter.toString();
- } catch (JsonParseException | IllegalStateException | IOException e) {
- throw new AssertionError("Failed to perform JSON encoding", e);
- }
- }
-
- /**
- * Creates the certificate.
- *
- * <p>This will return a serialized JSONObject in the following format:
- *
- * <pre>
- * {
- * // {
- * // // A string that identifies this cert
- * // "keyValue": "ZXBoZW1lcmFsUHVibGljS2V5"
- * // // string (UTC milliseconds since epoch)
- * // "expiration": "1520836260646",
- * // }
- * "signedKey": "... serialized JSON shown in comment above ...",
- * "signatures": ["signature1", "signature2", ...],
- * }
- * </pre>
- */
- public String create() throws GeneralSecurityException {
- String signedKey = jsonEncodeSignedKey(intermediateSigningKey, expiration);
- byte[] toSignBytes =
- PaymentMethodTokenUtil.toLengthValue(
- // The order of the parameters matters.
- senderId, protocolVersion, signedKey);
- ArrayList<String> signatures = new ArrayList<>();
- for (PublicKeySign signer : signers) {
- byte[] signature = signer.sign(toSignBytes);
- signatures.add(Base64.encode(signature));
- }
- return jsonEncodeCertificate(signedKey, signatures);
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/BUILD.bazel b/apps/paymentmethodtoken/src/test/BUILD.bazel
deleted file mode 100644
index c6f52d3..0000000
--- a/apps/paymentmethodtoken/src/test/BUILD.bazel
+++ /dev/null
@@ -1,43 +0,0 @@
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Tests
-
-java_library(
- name = "generator_test",
- testonly = 1,
- srcs = glob([
- "**/*.java",
- ]),
- deps = [
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:google_payments_public_keys_manager",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_constants",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_decrypt",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_encrypt",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_kem",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_sender",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_util",
- "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:sender_intermediate_cert_factory",
- "@maven//:com_google_code_gson_gson",
- "@maven//:com_google_http_client_google_http_client",
- "@maven//:joda_time_joda_time",
- "@maven//:junit_junit",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
- ],
-)
-
-gen_java_test_rules(
- test_files = glob(["**/*Test.java"],
- ),
- deps = [
- ":generator_test",
- ],
-)
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java
deleted file mode 100644
index 59cf9ad..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for {@link GooglePaymentsPublicKeysManager}. */
-@RunWith(JUnit4.class)
-public class GooglePaymentsPublicKeyManagerTest {
- @Test
- public void builderShouldReturnSingletonsWhenMatching() {
- assertSame(
- GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION,
- new GooglePaymentsPublicKeysManager.Builder().build());
- assertSame(
- GooglePaymentsPublicKeysManager.INSTANCE_TEST,
- new GooglePaymentsPublicKeysManager.Builder()
- .setKeysUrl(GooglePaymentsPublicKeysManager.KEYS_URL_TEST)
- .build());
- }
-
- @Test
- public void builderShouldReturnDifferentInstanceWhenNotMatchingSingletons() {
- assertNotSame(
- GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION,
- new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build());
- assertNotSame(
- GooglePaymentsPublicKeysManager.INSTANCE_TEST,
- new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build());
- }
-
- @Test
- public void builderShouldThrowIllegalArgumentExceptionWhenUrlIsNotHttps() {
- try {
- new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("http://abc").build();
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // expected.
- }
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java
deleted file mode 100644
index 70b8ed4..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertEquals;
-
-import java.util.ArrayList;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for the exact Json-Encoding produced.
- *
- * These tests test implementation details. Do not depend on the this. For example, the particular
- * ordering of the elements or the particular character escaping used may change in the future.
- * */
-@RunWith(JUnit4.class)
-public final class PaymentMethodJsonEncodingTest {
-
- @Test
- public void testExactOutputOfJsonEncodeCiphertext() throws Exception {
- byte[] ciphertext = "CiPhErTeXt".getBytes(UTF_8);
- byte[] tag = "taaag".getBytes(UTF_8);
- byte[] ephemeralPublicKey = "ephemeral Public Key".getBytes(UTF_8);
-
- String jsonEncodedCiphertext =
- PaymentMethodTokenHybridEncrypt.jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey);
-
- // JSONObject uses a HashMap, where the ordering is not defined. The ordering is however
- // deterministic. And for jsonEncodeCiphertext, the order happens to be first "encryptedMessage"
- // then "ephemeralPublicKey", and finally "tag". Also, JSONObject uses HTML-safe encoding.
- assertEquals(
- "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":"
- + "\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}",
- jsonEncodedCiphertext);
- }
-
- @Test
- public void testExactOutputOfJsonEncodeSignedMessage() throws Exception {
- String senderIntermediateCert =
- "{\"signedKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\""
- + ":\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]}";
- String version = "ECv1";
- String message =
- "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":\"ZXBoZW1l"
- + "cmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}";
- byte[] signature = "the signature".getBytes(UTF_8);
-
- String jsonEncodedSignedMessage =
- PaymentMethodTokenSender.jsonEncodeSignedMessage(
- message, version, signature, senderIntermediateCert);
-
- String expected =
- "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"intermediateSigningKey\":{\"signe"
- + "dKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":"
- + "\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]},\"protocolVersion\""
- + ":\"ECv1\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u00"
- + "3d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u00"
- + "3d\\\",\\\"tag\\\":\\\"dGFhYWc\\\\u003d\\\"}\"}";
- assertEquals(expected, jsonEncodedSignedMessage);
-
- String expected2 =
- "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"protocolVersion\":\"ECv1\",\"sign"
- + "edMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u003d\\\\u003d\\\","
- + "\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u003d\\\",\\\"tag\\"
- + "\":\\\"dGFhYWc\\\\u003d\\\"}\"}";
-
- String jsonEncodedSignedMessage2 =
- PaymentMethodTokenSender.jsonEncodeSignedMessage(message, version, signature, null);
- assertEquals(expected2, jsonEncodedSignedMessage2);
- }
-
- @Test
- public void testExactOutputOfJsonEncodedSignedKey() throws Exception {
- String intermediateSigningKey =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA"
- + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
- long expiration = 1520836260646L;
- assertEquals(
- "{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1"
- + "TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\",\"keyExpiration\""
- + ":\"1520836260646\"}",
- SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration));
- }
-
- @Test
- public void testExactOutputOfJsonEncodeCertificate() throws Exception {
- String intermediateSigningKey =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA"
- + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
- long expiration = 1520836260646L;
- String signedKey =
- SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration);
- ArrayList<String> signatures = new ArrayList<>();
- signatures.add("iTzFvzFRxyCw==");
- signatures.add("abcde090/+==");
- signatures.add("xyz");
- String expected =
- "{\"signedKey\":\"{\\\"keyValue\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE"
- + "/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRx"
- + "yCw\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":\\\"1520836260646\\\"}\",\"signatur"
- + "es\":[\"iTzFvzFRxyCw\\u003d\\u003d\",\"abcde090/+\\u003d\\u003d\",\"xyz\"]}";
- assertEquals(
- expected, SenderIntermediateCertFactory.jsonEncodeCertificate(signedKey, signatures));
- }
-
- @Test
- public void testExactOutputOfWeirdJsonEncodeCertificate() throws Exception {
- String intermediateSigningKey =
- "\"\\==";
- long expiration = -123;
- String signedKey =
- SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration);
- ArrayList<String> signatures = new ArrayList<>();
- signatures.add("");
- signatures.add("\\\"/+==");
- String expected =
- "{\"signedKey\":\"{\\\"keyValue\\\":\\\"\\\\\\\"\\\\\\\\\\\\u003d\\\\u003d"
- + "\\\",\\\"keyExpiration\\\":\\\"-123\\\"}\",\"signatures\":[\"\",\"\\\\\\\"/+\\u003d"
- + "\\u003d\"]}";
- assertEquals(
- expected, SenderIntermediateCertFactory.jsonEncodeCertificate(signedKey, signatures));
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java
deleted file mode 100644
index 387c3aa..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Random;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import java.util.Arrays;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenHybridDecrypt}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenHybridDecryptTest {
- @Test
- public void testModifyDecrypt() throws Exception {
- ECParameterSpec spec = EllipticCurves.getNistP256Params();
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
- keyGen.initialize(spec);
- KeyPair recipientKey = keyGen.generateKeyPair();
- ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
- ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
-
- HybridEncrypt hybridEncrypt =
- new PaymentMethodTokenHybridEncrypt(recipientPublicKey, ProtocolVersionConfig.EC_V1);
- HybridDecrypt hybridDecrypt =
- new PaymentMethodTokenHybridDecrypt(recipientPrivateKey, ProtocolVersionConfig.EC_V1);
- testModifyDecrypt(hybridEncrypt, hybridDecrypt);
- }
-
- public void testModifyDecrypt(HybridEncrypt hybridEncrypt, HybridDecrypt hybridDecrypt)
- throws Exception {
- byte[] plaintext = Random.randBytes(111);
- byte[] context = "context info".getBytes(UTF_8);
-
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
- byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context);
- assertArrayEquals(plaintext, decrypted);
-
- JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject();
-
- // Modify public key.
- byte[] kem =
- Base64.decode(
- json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString());
- for (int bytes = 0; bytes < kem.length; bytes++) {
- for (int bit = 0; bit < 8; bit++) {
- byte[] modifiedPublicKey = Arrays.copyOf(kem, kem.length);
- modifiedPublicKey[bytes] ^= (byte) (1 << bit);
- json.addProperty(
- PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY,
- Base64.encode(modifiedPublicKey));
- try {
- hybridDecrypt.decrypt(json.toString().getBytes(UTF_8), context);
- fail("Invalid ciphertext, should have thrown exception");
- } catch (GeneralSecurityException expected) {
- // Expected
- }
- }
- }
-
- // Modify payload.
- byte[] payload =
- Base64.decode(
- json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString());
- for (int bytes = 0; bytes < payload.length; bytes++) {
- for (int bit = 0; bit < 8; bit++) {
- byte[] modifiedPayload = Arrays.copyOf(payload, payload.length);
- modifiedPayload[bytes] ^= (byte) (1 << bit);
- json.addProperty(
- PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(modifiedPayload));
- try {
- hybridDecrypt.decrypt(json.toString().getBytes(UTF_8), context);
- fail("Invalid ciphertext, should have thrown exception");
- } catch (GeneralSecurityException expected) {
- // Expected
- }
- }
- }
-
- // Modify context.
- try {
- hybridDecrypt.decrypt(ciphertext, Arrays.copyOf(context, context.length - 1));
- fail("Invalid context, should have thrown exception");
- } catch (GeneralSecurityException expected) {
- // Expected
- }
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java
deleted file mode 100644
index 3fe9459..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Random;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import java.util.Set;
-import java.util.TreeSet;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenHybridEncrypt}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenHybridEncryptTest {
- @Test
- public void testBasicMultipleEncrypts() throws Exception {
- ECParameterSpec spec = EllipticCurves.getNistP256Params();
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
- keyGen.initialize(spec);
- KeyPair recipientKey = keyGen.generateKeyPair();
- ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
- ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
-
- HybridEncrypt hybridEncrypt =
- new PaymentMethodTokenHybridEncrypt(recipientPublicKey, ProtocolVersionConfig.EC_V1);
- HybridDecrypt hybridDecrypt =
- new PaymentMethodTokenHybridDecrypt(recipientPrivateKey, ProtocolVersionConfig.EC_V1);
- testBasicMultipleEncrypts(hybridEncrypt, hybridDecrypt);
- }
-
- public void testBasicMultipleEncrypts(HybridEncrypt hybridEncrypt, HybridDecrypt hybridDecrypt)
- throws Exception {
- byte[] plaintext = Random.randBytes(111);
- byte[] context = "context info".getBytes(StandardCharsets.UTF_8);
- // Makes sure that the encryption is randomized.
- Set<String> ciphertexts = new TreeSet<String>();
- for (int j = 0; j < 100; j++) {
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
- if (ciphertexts.contains(new String(ciphertext, StandardCharsets.UTF_8))) {
- throw new GeneralSecurityException("Encryption is not randomized");
- }
- ciphertexts.add(new String(ciphertext, StandardCharsets.UTF_8));
- byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context);
- assertArrayEquals(plaintext, decrypted);
- }
- assertEquals(100, ciphertexts.size());
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientTest.java
deleted file mode 100644
index 4bce9f5..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientTest.java
+++ /dev/null
@@ -1,1403 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.api.client.testing.http.MockHttpTransport;
-import com.google.api.client.testing.http.MockLowLevelHttpResponse;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenRecipient}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenRecipientTest {
-
- /**
- * Sample merchant public key.
- *
- * <p>Corresponds to public key of {@link #MERCHANT_PRIVATE_KEY_PKCS8_BASE64}
- *
- * <p>Created with:
- *
- * <pre>
- * openssl ec -in merchant-key.pem -pubout -text -noout 2> /dev/null | grep "pub:" -A5 \
- * | xxd -r -p | base64
- * </pre>
- */
- private static final String MERCHANT_PUBLIC_KEY_BASE64 =
- "BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=";
-
- /**
- * Sample merchant private key.
- *
- * <p>Corresponds to the private key of {@link #MERCHANT_PUBLIC_KEY_BASE64}
- *
- * <pre>
- * openssl pkcs8 -topk8 -inform PEM -outform PEM -in merchant-key.pem -nocrypt
- * </pre>
- */
- private static final String MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj"
- + "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx"
- + "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm";
-
- /** An alternative merchant private key used during the tests. */
- private static final String ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgOUIzccyJ3rTx6SVm"
- + "XrWdtwUP0NU26nvc8KIYw2GmYZKhRANCAAR5AjmTNAE93hQEQE+PryLlgr6Q7FXyN"
- + "XoZRk+1Fikhq61mFhQ9s14MOwGBxd5O6Jwn/sdUrWxkYk3idtNEN1Rz";
-
- /** Sample Google provided JSON with its public signing keys. */
- private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
- "{\n"
- + " \"keys\": [\n"
- + " {\n"
- + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQtlxizmLFynw"
- + "HcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\",\n"
- + " \"protocolVersion\": \"ECv1\"\n"
- + " },\n"
- + " {\n"
- + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM"
- + "43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==\",\n"
- + " \"keyExpiration\": \""
- + Instant.now().plus(Duration.standardDays(1)).getMillis()
- + "\",\n"
- + " \"protocolVersion\": \"ECv2\"\n"
- + " },\n"
- + " {\n"
- + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENXvYqxD5WayKYhuXQevdGdLA8i"
- + "fV4LsRS2uKvFo8wwyiwgQHB9DiKzG6T/P1Fu9Bl7zWy/se5Dy4wk1mJoPuxg==\",\n"
- + " \"keyExpiration\": \""
- + Instant.now().plus(Duration.standardDays(1)).getMillis()
- + "\",\n"
- + " \"protocolVersion\": \"ECv2SigningOnly\"\n"
- + " }\n"
- + " ]\n"
- + "}";
-
- /** Index within {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} of the ECv1 Google signing key. */
- private static final int INDEX_OF_GOOGLE_SIGNING_EC_V1 = 0;
-
- /** Index within {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} of the ECv2 Google signing key. */
- private static final int INDEX_OF_GOOGLE_SIGNING_EC_V2 = 1;
-
- /**
- * Index within {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} of the ECv2SigningOnly Google signing
- * key.
- */
- private static final int INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY = 2;
-
- /**
- * Sample Google private signing key for the ECv1 protocolVersion.
- *
- * <p>Corresponds to the ECv1 private key of the key in {@link
- * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZj/Dldxz8fvKVF5O"
- + "TeAtK6tY3G1McmvhMppe6ayW6GahRANCAAQ9icfBLy56BYB7BC2XGLOYsXKfAdzF"
- + "FPU8rTtwMDr8LixetUjVLNkJTHxTxLQuMytPqKt39Vbtt7czPq3+yu1F";
-
- /**
- * Sample Google private signing key for the ECv2 protocolVersion.
- *
- * <p>Corresponds to ECv2 private key of the key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
- + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
- + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
- /**
- * Sample Google intermediate public signing key for the ECv2 protocolVersion.
- *
- * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
- * X.509 standard.
- *
- * <p>The intermediate public key will be signed by {@link
- * #GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
- + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-
- /**
- * Sample Google intermediate private signing key for the ECv2 protocolVersion.
- *
- * <p>Corresponds to private key of the key in {@link
- * #GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
- + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
- + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
- /**
- * Sample Google private signing key for the ECv2SigningOnly protocolVersion.
- *
- * <p>Corresponds to ECv2SigningOnly private key of the key in {@link
- * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRi9hSdY+knJ08odnY"
- + "tZFMRi7ZYeMoasAijLhD4GiQ1yhRANCAAQ1e9irEPlZrIpiG5dB690Z0sDy"
- + "J9XguxFLa4q8WjzDDKLCBAcH0OIrMbpP8/UW70GXvNbL+x7kPLjCTWYmg+7G";
-
- /**
- * Sample Google intermediate public signing key for the ECv2SigningOnly protocolVersion.
- *
- * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
- * X.509 standard.
- *
- * <p>The intermediate public key will be signed by {@link
- * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64}.
- */
- private static final String
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8OaurwvbyYm8JWDgFPRTIDg0/"
- + "kcQTFAQ4txi5IP0AyM1QiagwRhDUfjpqZkpw8xt/DXwyWYM0DdHqoeV"
- + "TKqmYQ==";
-
- /**
- * Sample Google intermediate private signing key for the ECv2SigningOnly protocolVersion.
- *
- * <p>Corresponds to private key of the key in {@link
- * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
- */
- private static final String
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Jvpkq26tpZ0s"
- + "TTZVh4teEI41SnJdmkBzM8VZ5ZirE2hRANCAATw5q6vC9vJibwlYOAU"
- + "9FMgODT+RxBMUBDi3GLkg/QDIzVCJqDBGENR+OmpmSnDzG38NfDJZgz"
- + "QN0eqh5VMqqZh";
-
- private static final String RECIPIENT_ID = "someRecipient";
-
- private static final String PLAINTEXT = "plaintext";
-
- /**
- * The result of {@link #PLAINTEXT} encrypted with {@link #MERCHANT_PRIVATE_KEY_PKCS8_BASE64} and
- * signed with the only key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} using the ECv1
- * protocolVersion.
- */
- private static final String CIPHERTEXT_EC_V1 =
- "{"
- + "\"protocolVersion\":\"ECv1\","
- + "\"signedMessage\":"
- + ("\"{"
- + "\\\"tag\\\":\\\"ZVwlJt7dU8Plk0+r8rPF8DmPTvDiOA1UAoNjDV+SqDE\\\\u003d\\\","
- + "\\\"ephemeralPublicKey\\\":\\\"BPhVspn70Zj2Kkgu9t8+ApEuUWsI/zos5whGCQBlgOkuYagOis7"
- + "qsrcbQrcprjvTZO3XOU+Qbcc28FSgsRtcgQE\\\\u003d\\\","
- + "\\\"encryptedMessage\\\":\\\"12jUObueVTdy\\\"}\",")
- + "\"signature\":\"MEQCIDxBoUCoFRGReLdZ/cABlSSRIKoOEFoU3e27c14vMZtfAiBtX3pGMEpnw6mSAbnagC"
- + "CgHlCk3NcFwWYEyxIE6KGZVA\\u003d\\u003d\"}";
-
- private static final String ALTERNATE_PUBLIC_SIGNING_KEY =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU8E6JppGKFG40r5dDU1idHRN52NuwsemFzXZh1oUqh3bGUPgPioH+RoW"
- + "nmVSUQz1WfM2426w9f0GADuXzpUkcw==";
-
- private static final class MyPaymentMethodTokenRecipientKem
- implements PaymentMethodTokenRecipientKem {
- private final ECPrivateKey privateKey;
-
- public MyPaymentMethodTokenRecipientKem(String recipientPrivateKey)
- throws GeneralSecurityException {
- privateKey = PaymentMethodTokenUtil.pkcs8EcPrivateKey(recipientPrivateKey);
- }
-
- @Override
- public byte[] computeSharedSecret(final byte[] ephemeralPublicKey)
- throws GeneralSecurityException {
- ECPublicKey publicKey =
- EllipticCurves.getEcPublicKey(
- privateKey.getParams(),
- PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
- ephemeralPublicKey);
- return EllipticCurves.computeSharedSecret(privateKey, publicKey);
- }
- }
-
- @Test
- public void testShouldDecryptECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testShouldDecryptECV1WithNonStrictJsonEncoding() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- String ciphertextEcV1WithNonStrictJsonEncoding =
- "{"
- + "# comment \n" // python-style comment terminated with new line
- + "protocolVersion:'ECv1'," // protocolVersion has no quotes, ECv1 has single quotes
- + "/* a comment */" // c-style comment
- + "\"signedMessage\"=" // use = instead of :
- + "// another comment \n" // c-style comment terminated with new line
- + ("\"{"
- + "\\\"tag\\\":\\\"ZVwlJt7dU8Plk0+r8rPF8DmPTvDiOA1UAoNjDV+SqDE\\\\u003d\\\","
- + "\\\"ephemeralPublicKey\\\":\\\"BPhVspn70Zj2Kkgu9t8+ApEuUWsI/zos5whGCQBlgOkuYagOis7"
- + "qsrcbQrcprjvTZO3XOU+Qbcc28FSgsRtcgQE\\\\u003d\\\","
- + "\\\"encryptedMessage\\\":\\\"12jUObueVTdy\\\"}\";") // ; instead of ,
- + "\"signature\":\"MEQCIDxBoUCoFRGReLdZ/cABlSSRIKoOEFoU3e27c14vMZtfAiBtX3pGMEpnw6mSAbnagC"
- + "CgHlCk3NcFwWYEyxIE6KGZVA\\u003d\\u003d\"}";
-
- assertEquals(PLAINTEXT, recipient.unseal(ciphertextEcV1WithNonStrictJsonEncoding));
- }
-
- @Test
- public void testShouldDecryptECV1WhenUsingCustomKem() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientKem(
- new MyPaymentMethodTokenRecipientKem(MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testShouldDecryptECV1WhenFetchingSenderVerifyingKeys() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .fetchSenderVerifyingKeysWith(
- new GooglePaymentsPublicKeysManager.Builder()
- .setHttpTransport(
- new MockHttpTransport.Builder()
- .setLowLevelHttpResponse(
- new MockLowLevelHttpResponse()
- .setContent(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON))
- .build())
- .build())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testShouldTryAllKeysToDecryptECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testShouldTryAllCustomKemsToDecryptECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientKem(
- new MyPaymentMethodTokenRecipientKem(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
- .addRecipientKem(
- new MyPaymentMethodTokenRecipientKem(MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testShouldFailIfDecryptingWithDifferentKeyECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot decrypt", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfDecryptingWithDifferentCustomKemECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientKem(
- new MyPaymentMethodTokenRecipientKem(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
- .build();
-
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot decrypt", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfVerifyingWithDifferentKeyECV1() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- trustedKeysJson
- .get("keys")
- .getAsJsonArray()
- .get(INDEX_OF_GOOGLE_SIGNING_EC_V1)
- .getAsJsonObject()
- .addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldTryAllKeysToVerifySignatureECV1() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject correctKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
- JsonObject wrongKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject().deepCopy();
- wrongKey.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- JsonArray newKeys = new JsonArray();
- newKeys.add(wrongKey);
- newKeys.add(correctKey);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testShouldFailIfSignedECV1WithKeyForWrongProtocolVersion() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject correctKeyButWrongProtocol =
- keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
- correctKeyButWrongProtocol.addProperty(
- PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, "ECv2");
- JsonObject wrongKeyButRightProtocol = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
- wrongKeyButRightProtocol.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- wrongKeyButRightProtocol.addProperty(
- PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY,
- PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1);
- JsonArray newKeys = new JsonArray();
- newKeys.add(correctKeyButWrongProtocol);
- newKeys.add(wrongKeyButRightProtocol);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfNoSigningKeysForProtocolVersion() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
- key1.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, "ECv2");
- JsonObject key2 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-
- key2.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- key2.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, "ECv3");
- JsonArray newKeys = new JsonArray();
- newKeys.add(key1);
- newKeys.add(key2);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("no trusted keys are available for this protocol version", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfSignedMessageWasChangedInECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
- payload.addProperty(
- PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY,
- payload.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString() + " ");
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfWrongRecipientInECV1() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId("not " + RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfECV1SetsWrongProtocolVersion() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
- String invalidVersion = "ECv2";
- payload.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, invalidVersion);
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("invalid version: " + invalidVersion, e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfProtocolSetToAnInt() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
- payload.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, 1);
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- // expected
- }
- }
-
- @Test
- public void testShouldFailIfProtocolSetToAnFloat() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
- payload.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, 1.1);
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- // expected
- }
- }
-
- @Test
- public void testShouldSucceedIfMessageIsNotExpired() throws Exception {
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .recipientId(RECIPIENT_ID)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .build();
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- JsonObject plaintext = new JsonObject();
- plaintext.addProperty(
- "messageExpiration",
- // One day in the future
- String.valueOf(Instant.now().plus(Duration.standardDays(1)).getMillis()));
- plaintext.addProperty("someKey", "someValue");
- String ciphertext = sender.seal(plaintext.toString());
- JsonObject decrypted = JsonParser.parseString(recipient.unseal(ciphertext)).getAsJsonObject();
-
- assertEquals("someValue", decrypted.get("someKey").getAsString());
- }
-
- @Test
- public void testShouldFailIfMessageIsExpired() throws Exception {
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .recipientId(RECIPIENT_ID)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .build();
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- JsonObject expired = new JsonObject();
- expired.addProperty(
- "messageExpiration",
- // One day in the past
- String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-
- String ciphertext = sender.seal(expired.toString());
- try {
- recipient.unseal(ciphertext);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("expired payload", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfTrustedKeyIsExpiredInECV1() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
- key1.addProperty(
- "keyExpiration", // One day in the past
- String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
- JsonArray newKeys = new JsonArray();
- newKeys.add(key1);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(CIPHERTEXT_EC_V1);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("no trusted keys are available for this protocol version", e.getMessage());
- }
- }
-
- @Test
- public void testShouldSucceedIfKeyExpirationIsMissingInTrustedKeyIsExpiredForECV1()
- throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
- key1.remove("keyExpiration");
- JsonArray newKeys = new JsonArray();
- newKeys.add(key1);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
- }
-
- @Test
- public void testUnsealECV2() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
- }
-
- @Test
- public void testUnsealECV2WithCustomKem() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientKem(
- new MyPaymentMethodTokenRecipientKem(MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
- }
-
- @Test
- public void testShouldFailIfSignedMessageWasChangedInECV2() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
- payload.addProperty(
- PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY,
- payload.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString() + " ");
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfECV2UseWrongSenderId() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .senderId("not-" + PaymentMethodTokenConstants.GOOGLE_SENDER_ID)
- .build();
-
- try {
- recipient.unseal(sealECV2(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfVerifyingWithDifferentKeyECV2() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
- key1.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- JsonArray newKeys = new JsonArray();
- newKeys.add(key1);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(sealECV2(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfTrustedKeyIsExpiredInECV2() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
- key1.addProperty(
- "keyExpiration", // One day in the past
- String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
- JsonArray newKeys = new JsonArray();
- newKeys.add(key1);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(sealECV2(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("no trusted keys are available for this protocol version", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfKeyExpirationIsMissingInTrustedKeyECV2() throws Exception {
- // Key expiration is required for V2
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
- key1.remove("keyExpiration");
- JsonArray newKeys = new JsonArray();
- newKeys.add(key1);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(sealECV2(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("no trusted keys are available for this protocol version", e.getMessage());
- }
- }
-
- @Test
- public void testShouldTryAllKeysToVerifySignatureECV2() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject correctKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
- JsonObject wrongKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject().deepCopy();
- wrongKey.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- JsonArray newKeys = new JsonArray();
- newKeys.add(wrongKey);
- newKeys.add(correctKey);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
- }
-
- @Test
- public void testShouldFailIfSignedKeyWasChangedInECV2() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
- JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
- intermediateSigningKey.addProperty(
- "signedKey", intermediateSigningKey.get("signedKey").getAsString() + " ");
- payload.add("intermediateSigningKey", intermediateSigningKey);
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSignatureForSignedKeyIsIncorrectInECV2() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
- JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
- JsonArray signatures = intermediateSigningKey.get("signatures").getAsJsonArray();
- String correctSignature = signatures.get(0).getAsString();
- byte[] wrongSignatureBytes = Base64.decode(correctSignature);
- wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
- JsonArray newSignatures = new JsonArray();
- newSignatures.add(Base64.encode(wrongSignatureBytes));
- intermediateSigningKey.add("signatures", newSignatures);
- payload.add("intermediateSigningKey", intermediateSigningKey);
-
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldTryVerifyingAllSignaturesForSignedKeyInECV2() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
- JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
- JsonArray signatures = intermediateSigningKey.get("signatures").getAsJsonArray();
- String correctSignature = signatures.get(0).getAsString();
- byte[] wrongSignatureBytes = Base64.decode(correctSignature);
- wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
- JsonArray newSignatures = new JsonArray();
- newSignatures.add(Base64.encode(wrongSignatureBytes));
- newSignatures.add(correctSignature);
- intermediateSigningKey.add("signatures", newSignatures);
- payload.add("intermediateSigningKey", intermediateSigningKey);
-
- assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
- }
-
- @Test
- public void testShouldThrowIfECV2UseWrongRecipientId() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId("not" + RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- try {
- recipient.unseal(sealECV2(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldAcceptNonExpiredECV2Message() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- JsonObject payload = new JsonObject();
- payload.addProperty(
- "messageExpiration",
- // One day in the future
- String.valueOf(Instant.now().plus(Duration.standardDays(1)).getMillis()));
- String plaintext = payload.toString();
- assertEquals(plaintext, recipient.unseal(sealECV2(plaintext)));
- }
-
- @Test
- public void testShouldFailIfECV2MessageIsExpired() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- JsonObject payload = new JsonObject();
- payload.addProperty(
- "messageExpiration",
- // One day in the past
- String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-
- String ciphertext = sealECV2(payload.toString());
- try {
- recipient.unseal(ciphertext);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("expired payload", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfIntermediateSigningKeyIsExpiredInECV2() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- // Expiration date in the past.
- .expiration(Instant.now().minus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .build();
-
- try {
- recipient.unseal(sender.seal(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("expired intermediateSigningKey", e.getMessage());
- }
- }
-
- private static String sealECV2(String plaintext) throws GeneralSecurityException {
- return new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .build()
- .seal(plaintext);
- }
-
- @Test
- public void testVerifyECV2SigningOnly() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(signECV2SigningOnly(PLAINTEXT)));
- }
-
- @Test
- public void testShouldFailIfSignedMessageWasChangedInECV2SigningOnly() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
- JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
- payload.addProperty(
- PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY,
- payload.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString() + " ");
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfECV2SigningOnlyUseWrongSenderId() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .senderId("not-" + PaymentMethodTokenConstants.GOOGLE_SENDER_ID)
- .build();
-
- try {
- recipient.unseal(signECV2SigningOnly(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfVerifyingWithDifferentKeyECV2SigningOnly() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
- key.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- JsonArray newKeys = new JsonArray();
- newKeys.add(key);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .build();
-
- try {
- recipient.unseal(signECV2SigningOnly(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfTrustedKeyIsExpiredInECV2SigningOnly() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
- key.addProperty(
- "keyExpiration", // One day in the past
- String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
- JsonArray newKeys = new JsonArray();
- newKeys.add(key);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .build();
-
- try {
- recipient.unseal(signECV2SigningOnly(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("no trusted keys are available for this protocol version", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfKeyExpirationIsMissingInTrustedKeyECV2SigningOnly() throws Exception {
- // Key expiration is required for ECv2SigningOnly
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject key = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
- key.remove("keyExpiration");
- JsonArray newKeys = new JsonArray();
- newKeys.add(key);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .build();
-
- try {
- recipient.unseal(signECV2SigningOnly(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("no trusted keys are available for this protocol version", e.getMessage());
- }
- }
-
- @Test
- public void testShouldTryAllKeysToVerifySignatureECV2SigningOnly() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
- JsonObject correctKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
- JsonObject wrongKey =
- keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject().deepCopy();
- wrongKey.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
- JsonArray newKeys = new JsonArray();
- newKeys.add(wrongKey);
- newKeys.add(correctKey);
- trustedKeysJson.add("keys", newKeys);
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(trustedKeysJson.toString())
- .recipientId(RECIPIENT_ID)
- .build();
-
- assertEquals(PLAINTEXT, recipient.unseal(signECV2SigningOnly(PLAINTEXT)));
- }
-
- @Test
- public void testShouldFailIfSignedKeyWasChangedInECV2SigningOnly() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
-
- JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
- JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
- intermediateSigningKey.addProperty(
- "signedKey", intermediateSigningKey.get("signedKey").getAsString() + " ");
- payload.add("intermediateSigningKey", intermediateSigningKey);
-
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSignatureForSignedKeyIsIncorrectInECV2SigningOnly()
- throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
- JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
- JsonArray signatures =
- payload.get("intermediateSigningKey").getAsJsonObject().get("signatures").getAsJsonArray();
- String correctSignature = signatures.get(0).getAsString();
- byte[] wrongSignatureBytes = Base64.decode(correctSignature);
- wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
- JsonArray newSignatures = new JsonArray();
- newSignatures.add(Base64.encode(wrongSignatureBytes));
- JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
- intermediateSigningKey.add("signatures", newSignatures);
- payload.add("intermediateSigningKey", intermediateSigningKey);
-
- try {
- recipient.unseal(payload.toString());
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldTryVerifyingAllSignaturesForSignedKeyInECV2SigningOnly() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
- JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
- JsonArray signatures =
- payload.get("intermediateSigningKey").getAsJsonObject().get("signatures").getAsJsonArray();
- String correctSignature = signatures.get(0).getAsString();
- byte[] wrongSignatureBytes = Base64.decode(correctSignature);
- wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
- JsonArray newSignatures = new JsonArray();
- newSignatures.add(Base64.encode(wrongSignatureBytes));
- newSignatures.add(correctSignature);
- JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
- intermediateSigningKey.add("signatures", newSignatures);
- payload.add("intermediateSigningKey", intermediateSigningKey);
-
- assertEquals(PLAINTEXT, recipient.unseal(signECV2SigningOnly(PLAINTEXT)));
- }
-
- @Test
- public void testShouldThrowIfECV2SigningOnlyUseWrongRecipientId() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId("not" + RECIPIENT_ID)
- .build();
-
- try {
- recipient.unseal(signECV2SigningOnly(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("cannot verify signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldAcceptNonExpiredECV2SigningOnlyMessage() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
-
- JsonObject payload = new JsonObject();
- payload.addProperty(
- "messageExpiration",
- // One day in the future
- String.valueOf(Instant.now().plus(Duration.standardDays(1)).getMillis()));
- String plaintext = payload.toString();
- assertEquals(plaintext, recipient.unseal(signECV2SigningOnly(plaintext)));
- }
-
- @Test
- public void testShouldFailIfECV2SigningOnlyMessageIsExpired() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
-
- JsonObject payload = new JsonObject();
- payload.addProperty(
- "messageExpiration",
- // One day in the past
- String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
- String ciphertext = signECV2SigningOnly(payload.toString());
- try {
- recipient.unseal(ciphertext);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("expired payload", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfIntermediateSigningKeyIsExpiredInECV2SigningOnly() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(
- PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- // Expiration date in the past.
- .expiration(Instant.now().minus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .build();
-
- try {
- recipient.unseal(sender.seal(PLAINTEXT));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("expired intermediateSigningKey", e.getMessage());
- }
- }
-
- private static String signECV2SigningOnly(String plaintext) throws GeneralSecurityException {
- return new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .build()
- .seal(plaintext);
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSenderTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSenderTest.java
deleted file mode 100644
index 5ca98a0..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSenderTest.java
+++ /dev/null
@@ -1,577 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.subtle.EllipticCurves;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenSender}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenSenderTest {
- private static final String MERCHANT_PUBLIC_KEY_BASE64 =
- "BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=";
- /**
- * Created with:
- *
- * <pre>
- * openssl pkcs8 -topk8 -inform PEM -outform PEM -in merchant-key.pem -nocrypt
- * </pre>
- */
- private static final String MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj"
- + "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx"
- + "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm";
-
- /** Sample Google provided JSON with its public signing keys. */
- private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
- "{\n"
- + " \"keys\": [\n"
- + " {\n"
- + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQtlxizmLFynw"
- + "HcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\",\n"
- + " \"protocolVersion\": \"ECv1\"\n"
- + " },\n"
- + " {\n"
- + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM"
- + "43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==\",\n"
- + " \"keyExpiration\": \""
- + Instant.now().plus(Duration.standardDays(1)).getMillis()
- + "\",\n"
- + " \"protocolVersion\": \"ECv2\"\n"
- + " },\n"
- + " {\n"
- + " \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENXvYqxD5WayKYhuXQevdGdLA8i"
- + "fV4LsRS2uKvFo8wwyiwgQHB9DiKzG6T/P1Fu9Bl7zWy/se5Dy4wk1mJoPuxg==\",\n"
- + " \"keyExpiration\": \""
- + Instant.now().plus(Duration.standardDays(1)).getMillis()
- + "\",\n"
- + " \"protocolVersion\": \"ECv2SigningOnly\"\n"
- + " }\n"
- + " ]\n"
- + "}";
-
- /**
- * Sample Google private signing key for the ECv1 protocolVersion.
- *
- * <p>Corresponds to the ECv1 private key of the key in {@link
- * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZj/Dldxz8fvKVF5O"
- + "TeAtK6tY3G1McmvhMppe6ayW6GahRANCAAQ9icfBLy56BYB7BC2XGLOYsXKfAdzF"
- + "FPU8rTtwMDr8LixetUjVLNkJTHxTxLQuMytPqKt39Vbtt7czPq3+yu1F";
-
- /**
- * Sample Google private signing key for the ECv2 protocolVersion.
- *
- * <p>Corresponds to ECv2 private key of the key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
- + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
- + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
- /**
- * Sample Google intermediate public signing key for the ECv2 protocolVersion.
- *
- * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
- * X.509 standard.
- *
- * <p>The intermediate public key will be signed by {@link
- * #GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
- + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-
- /**
- * Sample Google intermediate private signing key for the ECv2 protocolVersion.
- *
- * <p>Corresponds to private key of the key in {@link
- * #GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
- + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
- + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
- /**
- * Sample Google private signing key for the ECV2SigningOnly protocolVersion.
- *
- * <p>Corresponds to ECV2SigningOnly private key of the key in {@link
- * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRi9hSdY+knJ08odnY"
- + "tZFMRi7ZYeMoasAijLhD4GiQ1yhRANCAAQ1e9irEPlZrIpiG5dB690Z0sDy"
- + "J9XguxFLa4q8WjzDDKLCBAcH0OIrMbpP8/UW70GXvNbL+x7kPLjCTWYmg+7G";
-
- /**
- * Sample Google intermediate public signing key for the ECV2SigningOnly protocolVersion.
- *
- * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
- * X.509 standard.
- *
- * <p>The intermediate public key will be signed by {@link
- * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64}.
- */
- private static final String
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8OaurwvbyYm8JWDgFPRTIDg0/"
- + "kcQTFAQ4txi5IP0AyM1QiagwRhDUfjpqZkpw8xt/DXwyWYM0DdHqoeV"
- + "TKqmYQ==";
-
- /**
- * Sample Google intermediate private signing key for the ECV2SigningOnly protocolVersion.
- *
- * <p>Corresponds to private key of the key in {@link
- * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
- */
- private static final String
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Jvpkq26tpZ0s"
- + "TTZVh4teEI41SnJdmkBzM8VZ5ZirE2hRANCAATw5q6vC9vJibwlYOAU"
- + "9FMgODT+RxBMUBDi3GLkg/QDIzVCJqDBGENR+OmpmSnDzG38NfDJZgz"
- + "QN0eqh5VMqqZh";
-
- private static final String RECIPIENT_ID = "someRecipient";
-
- @Test
- public void testECV1WithPrecomputedKeys() throws Exception {
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .recipientId(RECIPIENT_ID)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .build();
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- String plaintext = "blah";
- assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
- }
-
- @Test
- public void testECV1WithTestdataSeal() throws Exception {
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- // Seal where "=" has been escaped by \u003d.
- // - tag is "bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8=", = gets escapced by \u003d, so
- // signedMessage contains "tag": "bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8\u003d"
- // - this gets escaped in the 2nd encoding with \ -> \\ and " -> \". So the final seal
- // contains \"tag\":\"bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8\\u003d\"
- // Note that to encode this string in Java, we have to escape \ -> \\ and " -> \" again.
- String testdataSeal =
- "{\"signedMessage\":\"{\\\"tag\\\":\\\"bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8\\\\u003d"
- + "\\\",\\\"ephemeralPublicKey\\\":\\\"BLyk1Iwcx+yRQbCSsZgAwb8s/ChNDTlcCovgL5/37qDdia3x"
- + "PM5GUb+HmbW9bF/c12p0ySxtU/MDcNkJZ0nWCVs\\\\u003d\\\",\\\"encryptedMessage\\\":\\\"ue"
- + "63Gg\\\\u003d\\\\u003d\\\"}\",\"protocolVersion\":\"ECv1\",\"signature\":\"MEQCIFFdW"
- + "ve35+jh7CT9QC9W6Leqx32P41oNxG2NDm6PaY1fAiAKHvklWHDPiLKYPb0zyXRGIl6GYvfQ3LAl1z5sR3CbS"
- + "w\\u003d\\u003d\"}";
-
- // Seal where "=" has not been escaped, but " and \ have to be escaped as before.
- String testdataSeal2 =
- "{\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"Ih3e7g==\\\",\\\"tag\\\":\\\"GlZ332kL5u"
- + "ZICWrCsSSQ6KrFFqQyKI84SH2Wh6UTv8c=\\\",\\\"ephemeralPublicKey\\\":\\\"BBb/VaSEYJphhs"
- + "ma1X24QrjdFr/DHo7IDu8owi6NR0tW9p5F9jn6wxu5by5Rs/bYkVdr8HNT9+MEpBOnL7Si/dQ=\\\"}\",\""
- + "protocolVersion\":\"ECv1\",\"signature\":\"MEYCIQD6rOV4l9pm/MDr2jPkqnU2GSHbbnUaLYQow"
- + "/0Y+S1axAIhAKUUO7N9eoBNuXySDDWDYrdu7r0IHxeeFtkubDxhplYU\"}";
-
- // Seal where "=" has only been escaped in the outer encoding:
- // - tag is "Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE=", so
- // signedMessage contains "tag": "Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE="
- // - this gets escaped in the 2nd encoding with \ -> \\, " -> \" and = -> \u003d. So the final
- // seal contains \"tag\":\"Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE\u003d\"
- // Note that to encode this string in Java, we have to escape \ -> \\ and " -> \" again.
- String testdataSeal3 =
- "{\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"eTBsng\\u003d\\u003d\\\",\\\"tag\\\":\\"
- + "\"Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE\\u003d\\\",\\\"ephemeralPublicKey\\\":"
- + "\\\"BGPS6h2ddaOA+H71e28xZ2OQZBJuVrCK3qUXc6G6ykHTr8ab9pmS7B87n9jg8qwYAWpFcRNgC2fxnrPL"
- + "+p2Gk5U\\u003d\\\"}\",\"protocolVersion\":\"ECv1\",\"signature\":\"MEUCIQC+8NTo1xbem"
- + "5lPhXuEwcYc0W03jdj3Q1YNI4XvoVAbuAIgT5WXy1wZ2GNXoaLauNGo0iHeOjsZT3wYUziZPHkSAlk\\u003"
- + "d\"}";
-
- // Weird token where for no reason the characters { -> \u007b and s -> \u0073 have been escaped.
- String testdataSeal4 =
- "{\"\\u0073ignedMe\\u0073\\u0073age\":\"\\u007b\\\"encryptedMe\\\\u0073\\\\u0073age\\\":\\"
- + "\"tfAPcA\\\\u003d\\\\u003d\\\",\\\"tag\\\":\\\"bB72KLdziA4Oca77w7g7a4fbKpgFXVrtUr56W"
- + "NpIL6M\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"BKYBrgWkOP7gqCcIqRjT1t0HjMdbEIMeP"
- + "82VtfC7IaCNbHgL9vojnQ/Yxy8Kutn+MCvG3gRdTxGN+Pwl+1QjM6w\\\\u003d\\\"}\",\"protocolVer"
- + "\\u0073ion\":\"ECv1\",\"\\u0073ignature\":\"MEUCIHDW7JS51iy1LeyiNri7tNqe0HYdPwmDoi/M"
- + "j2RD+3f1AiEA9FBSpqDhQzEvn849IrxXWQlIkuJdUfLE1NxCBQ8mCj0\\u003d\"}";
-
- String plaintext = "blah";
- assertEquals(plaintext, recipient.unseal(testdataSeal));
- assertEquals(plaintext, recipient.unseal(testdataSeal2));
- assertEquals(plaintext, recipient.unseal(testdataSeal3));
- assertEquals(plaintext, recipient.unseal(testdataSeal4));
- }
-
- @Test
- public void testECV2WithPrecomputedKeys() throws Exception {
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .build();
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
- .build();
-
- String plaintext = "blah";
- assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
- }
-
- @Test
- public void testECV2SigningOnlyWithPrecomputedKeys() throws Exception {
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(
- PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .build();
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .recipientId(RECIPIENT_ID)
- .build();
-
- String plaintext = "blah";
- assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
- }
-
- @Test
- public void testShouldThrowWithUnsupportedProtocolVersion() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder().protocolVersion("ECv99").build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals("invalid version: ECv99", expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSignedIntermediateSigningKeyIsSetForECV1() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateCert(newSignedIntermediateSigningKey())
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must not set signed sender's intermediate signing key using "
- + "Builder.senderIntermediateCert",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfIntermediateSigningKeyIsSetForECV1() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must not set sender's intermediate signing key using "
- + "Builder.senderIntermediateSigningKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSenderSigningKeyIsSetForECV2() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must not set sender's signing key using Builder.senderSigningKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSignedIntermediateSigningKeyIsNotSetForECV2() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- // no calls to senderIntermediateCert
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must set signed sender's intermediate signing key using "
- + "Builder.senderIntermediateCert",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfIntermediateSigningKeyIsNotSetForECV2() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- // no calls to senderIntermediateSigningKey
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must set sender's intermediate signing key using Builder.senderIntermediateSigningKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSenderSigningKeyIsNotSetForECV1() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
- // no calls to senderSigningKey
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must set sender's signing key using Builder.senderSigningKey", expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSenderSigningKeyIsSetForECV2SigningOnly() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must not set sender's signing key using Builder.senderSigningKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfSignedIntermediateSigningKeyIsNotSetForECV2SigningOnly()
- throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- // no calls to senderIntermediateCert
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must set signed sender's intermediate signing key using "
- + "Builder.senderIntermediateCert",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfRecipientPublicKeyIsSetForECV2SigningOnly() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- // no calls to senderIntermediateCert
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
- .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
- .senderIntermediateCert(
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(
- GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build()
- .create())
- .recipientId(RECIPIENT_ID)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must not set recipient's public key using Builder.recipientPublicKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void testShouldThrowIfIntermediateSigningKeyIsNotSetForECV2SigningOnly() throws Exception {
- try {
- new PaymentMethodTokenSender.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- // no calls to senderIntermediateSigningKey
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must set sender's intermediate signing key using Builder.senderIntermediateSigningKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void testSendReceive() throws Exception {
- ECParameterSpec spec = EllipticCurves.getNistP256Params();
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
- keyGen.initialize(spec);
- String senderId = "foo";
- String recipientId = "bar";
-
- KeyPair senderKey = keyGen.generateKeyPair();
- ECPublicKey senderPublicKey = (ECPublicKey) senderKey.getPublic();
- ECPrivateKey senderPrivateKey = (ECPrivateKey) senderKey.getPrivate();
-
- KeyPair recipientKey = keyGen.generateKeyPair();
- ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
- ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
-
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .senderId(senderId)
- .senderSigningKey(senderPrivateKey)
- .recipientId(recipientId)
- .recipientPublicKey(recipientPublicKey)
- .build();
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderId(senderId)
- .addSenderVerifyingKey(senderPublicKey)
- .recipientId(recipientId)
- .addRecipientPrivateKey(recipientPrivateKey)
- .build();
-
- String plaintext = "blah";
- assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
- }
-
- @Test
- public void testWithKeysGeneratedByRecipientKeyGen() throws Exception {
- ECParameterSpec spec = EllipticCurves.getNistP256Params();
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
- keyGen.initialize(spec);
- String senderId = "foo";
- String recipientId = "bar";
-
- KeyPair senderKey = keyGen.generateKeyPair();
- ECPublicKey senderPublicKey = (ECPublicKey) senderKey.getPublic();
- ECPrivateKey senderPrivateKey = (ECPrivateKey) senderKey.getPrivate();
-
- // The keys here are generated by PaymentMethodTokenRecipientKeyGen.
- String recipientPrivateKey =
- "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAP5/1502pXYdMion22yiWK"
- + "GoTBJN/wAAfdjBU6puyEMw==";
- String recipientPublicKey =
- "BJ995jnw2Ppn4BMP/ZKtlTOOIBQC+/L3PDcFRjowZuCkRqUZ/kGWE8c+zimZNHOZPzLB"
- + "NVGJ3V8M/fM4g4o02Mc=";
-
- PaymentMethodTokenSender sender =
- new PaymentMethodTokenSender.Builder()
- .senderId(senderId)
- .senderSigningKey(senderPrivateKey)
- .recipientId(recipientId)
- .rawUncompressedRecipientPublicKey(recipientPublicKey)
- .build();
-
- PaymentMethodTokenRecipient recipient =
- new PaymentMethodTokenRecipient.Builder()
- .senderId(senderId)
- .addSenderVerifyingKey(senderPublicKey)
- .recipientId(recipientId)
- .addRecipientPrivateKey(recipientPrivateKey)
- .build();
-
- String plaintext = "blah";
- assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
- }
-
- private String newSignedIntermediateSigningKey() throws GeneralSecurityException {
- return new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build()
- .create();
- }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java
deleted file mode 100644
index 566ebde..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for {@link SenderIntermediateCertFactory}. */
-@RunWith(JUnit4.class)
-public class SenderIntermediateCertFactoryTest {
-
- /**
- * Sample Google private signing key for the ECv2 protocolVersion.
- *
- * <p>Base64 version of the private key encoded in PKCS8 format.
- */
- private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
- + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
- + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
- /**
- * Sample Google intermediate public signing key for the ECv2 protocolVersion.
- *
- * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
- * X.509 standard.
- */
- private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
- + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-
- @Test
- public void shouldProduceSenderIntermediateCertJson() throws Exception {
- String encoded =
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .expiration(123456)
- .build()
- .create();
-
- JsonObject decodedSignedIntermediateSigningKey =
- JsonParser.parseString(encoded).getAsJsonObject();
- assertTrue(decodedSignedIntermediateSigningKey.get("signedKey").isJsonPrimitive());
- assertTrue(decodedSignedIntermediateSigningKey.get("signatures").isJsonArray());
- assertEquals(2, decodedSignedIntermediateSigningKey.size());
-
- JsonObject signedKey =
- JsonParser.parseString(decodedSignedIntermediateSigningKey.get("signedKey").getAsString())
- .getAsJsonObject();
- assertTrue(signedKey.get("keyValue").getAsJsonPrimitive().isString());
- assertTrue(signedKey.get("keyExpiration").getAsJsonPrimitive().isString());
- assertEquals(2, signedKey.size());
- assertEquals(
- GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64,
- signedKey.get("keyValue").getAsString());
- assertEquals("123456", signedKey.get("keyExpiration").getAsString());
- assertEquals(1, decodedSignedIntermediateSigningKey.get("signatures").getAsJsonArray().size());
- assertFalse(
- decodedSignedIntermediateSigningKey
- .get("signatures")
- .getAsJsonArray()
- .get(0)
- .getAsString()
- .isEmpty());
- }
-
- @Test
- public void shouldThrowIfExpirationNotSet() throws Exception {
- try {
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- // no expiration
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals("must set expiration using Builder.expiration", expected.getMessage());
- }
- }
-
- @Test
- public void shouldThrowIfExpirationIsNegative() throws Exception {
- try {
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .expiration(-1)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals("invalid negative expiration", expected.getMessage());
- }
- }
-
- @Test
- public void shouldThrowIfNoSenderSigningKeyAdded() throws Exception {
- try {
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- // no call to addSenderSigningKey
- .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals(
- "must add at least one sender's signing key using Builder.addSenderSigningKey",
- expected.getMessage());
- }
- }
-
- @Test
- public void shouldSupportECV2SigningOnly() throws Exception {
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
- .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
- .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
- .expiration(123456)
- .build();
- }
-
- @Test
- public void shouldThrowIfInvalidProtocolVersionSet() throws Exception {
- try {
- new SenderIntermediateCertFactory.Builder()
- .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
- .build();
- fail("Should have thrown!");
- } catch (IllegalArgumentException expected) {
- assertEquals("invalid version: ECv1", expected.getMessage());
- }
- }
-}
diff --git a/apps/rewardedads/BUILD.bazel b/apps/rewardedads/BUILD.bazel
deleted file mode 100644
index a8f1330..0000000
--- a/apps/rewardedads/BUILD.bazel
+++ /dev/null
@@ -1,15 +0,0 @@
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-gen_maven_jar_rules(
- name = "maven",
- doctitle = "Tink Cryptography API for Google Mobile Rewarded Video Ads SSV",
- manifest_lines = [
- "Automatic-Module-Name: com.google.crypto.tink.apps.rewardedads",
- ],
- root_packages = ["com.google.crypto.tink.apps.rewardedads"],
- deps = ["//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier"],
-)
diff --git a/apps/rewardedads/README.md b/apps/rewardedads/README.md
deleted file mode 100644
index b92e374..0000000
--- a/apps/rewardedads/README.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# An implementation of Google AdMob Rewarded Ads
-
-This app implements the verifier side of Server-Side Verification of Google
-AdMob Rewarded Ads.
-
-## Latest Release
-
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-rewardedads/1.7.0).
-
-The Maven group ID is `com.google.crypto.tink`, and the artifact ID is
-`apps-rewardedads`.
-
-To add a dependency using Maven:
-
-```xml
-<dependency>
- <groupId>com.google.crypto.tink</groupId>
- <artifactId>apps-rewardedads</artifactId>
- <version>1.7.0</version>
-</dependency>
-```
-
-## Snapshots
-
-Snapshots of this app built from the master branch are available through Maven
-using version `HEAD-SNAPSHOT`. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-rewardedads/HEAD-SNAPSHOT).
-
-To add a dependency using Maven:
-
-```xml
-<repositories>
-<repository>
- <id>sonatype-snapshots</id>
- <name>sonatype-snapshots</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
- <snapshots>
- <enabled>true</enabled>
- <updatePolicy>always</updatePolicy>
- </snapshots>
- <releases>
- <updatePolicy>always</updatePolicy>
- </releases>
-</repository>
-</repositories>
-
-<dependency>
- <groupId>com.google.crypto.tink</groupId>
- <artifactId>apps-rewardedads</artifactId>
- <version>HEAD-SNAPSHOT</version>
-</dependency>
-```
diff --git a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel b/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel
deleted file mode 100644
index a44132c..0000000
--- a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-java_library(
- name = "rewarded_ads_verifier",
- srcs = ["RewardedAdsVerifier.java"],
- deps = [
- "@maven//:com_google_code_gson_gson",
- "@maven//:com_google_http_client_google_http_client",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:enums",
- "@tink_java//src/main/java/com/google/crypto/tink/util:keys_downloader",
- ],
-)
diff --git a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifier.java b/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifier.java
deleted file mode 100644
index 85d26df..0000000
--- a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifier.java
+++ /dev/null
@@ -1,325 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.rewardedads;
-
-import com.google.api.client.http.javanet.NetHttpTransport;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaVerifyJce;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.crypto.tink.subtle.Enums.HashType;
-import com.google.crypto.tink.util.KeysDownloader;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPublicKey;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * An implementation of the verifier side of Server-Side Verification of Google AdMob Rewarded Ads.
- *
- * <p>Typical usage:
- *
- * <pre>{@code
- * RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
- * .fetchVerifyingPublicKeysWith(
- * RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
- * .build();
- * String rewardUrl = ...;
- * verifier.verify(rewardUrl);
- * }</pre>
- *
- * <p>This usage ensures that you always have the latest public keys, even when the keys were
- * recently rotated. It will fetch and cache the latest public keys from {#PUBLIC_KEYS_URL_PROD}.
- * When the cache expires, it will re-fetch the public keys. When initializing your server, we also
- * recommend that you call {@link KeysDownloader#refreshInBackground()} of {@link
- * RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD} to proactively fetch the public keys.
- *
- * <p>If you've already downloaded the public keys and have other means to manage key rotation, you
- * can use {@link RewardedAdsVerifier.Builder#setVerifyingPublicKeys} to set the public keys. The
- * Builder also allows you to customize other properties.
- */
-public final class RewardedAdsVerifier {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
- /** Default HTTP transport used by this class. */
- private static final NetHttpTransport DEFAULT_HTTP_TRANSPORT =
- new NetHttpTransport.Builder().build();
-
- private static final Executor DEFAULT_BACKGROUND_EXECUTOR = Executors.newCachedThreadPool();
- private final List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders;
-
- public static final String SIGNATURE_PARAM_NAME = "signature=";
- public static final String KEY_ID_PARAM_NAME = "key_id=";
-
- /** URL to fetch keys for environment production. */
- public static final String PUBLIC_KEYS_URL_PROD =
- "https://www.gstatic.com/admob/reward/verifier-keys.json";
-
- /** URL to fetch keys for environment test. */
- public static final String PUBLIC_KEYS_URL_TEST =
- "https://www.gstatic.com/admob/reward/verifier-keys-test.json";
-
- /**
- * Instance configured to talk to fetch keys from production environment (from {@link
- * #PUBLIC_KEYS_URL_PROD}).
- */
- public static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_PROD =
- new KeysDownloader(DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, PUBLIC_KEYS_URL_PROD);
- /**
- * Instance configured to talk to fetch keys from test environment (from {@link
- * #PUBLIC_KEYS_URL_TEST}).
- */
- public static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_TEST =
- new KeysDownloader(DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, PUBLIC_KEYS_URL_TEST);
-
- RewardedAdsVerifier(List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders)
- throws GeneralSecurityException {
- if (verifyingPublicKeysProviders == null || verifyingPublicKeysProviders.isEmpty()) {
- throw new IllegalArgumentException(
- "must set at least one way to get verifying key using"
- + " Builder.fetchVerifyingPublicKeysWith or Builder.setVerifyingPublicKeys");
- }
- this.verifyingPublicKeysProviders = verifyingPublicKeysProviders;
- }
-
- private RewardedAdsVerifier(Builder builder) throws GeneralSecurityException {
- this(builder.verifyingPublicKeysProviders);
- }
-
- /**
- * Verifies that {@code rewardUrl} has a valid signature.
- *
- * <p>This method requires that the name of the last two query parameters of {@code rewardUrl} are
- * {@link #SIGNATURE_PARAM_NAME} and {@link #KEY_ID_PARAM_NAME} in that order.
- */
- public void verify(String rewardUrl) throws GeneralSecurityException {
- URI uri;
- try {
- uri = new URI(rewardUrl);
- } catch (URISyntaxException ex) {
- throw new GeneralSecurityException(ex);
- }
- String queryString = uri.getQuery();
- int i = queryString.indexOf(SIGNATURE_PARAM_NAME);
- if (i <= 0 || queryString.charAt(i - 1) != '&') {
- throw new GeneralSecurityException(
- "signature and key id must be the last two query parameters");
- }
- byte[] tbsData =
- queryString.substring(0, i - 1 /* i - 1 instead of i because of & */).getBytes(UTF_8);
-
- String sigAndKeyId = queryString.substring(i);
- i = sigAndKeyId.indexOf(KEY_ID_PARAM_NAME);
- if (i == -1 || sigAndKeyId.charAt(i - 1) != '&') {
- throw new GeneralSecurityException(
- "signature and key id must be the last two query parameters");
- }
- String sig =
- sigAndKeyId.substring(
- SIGNATURE_PARAM_NAME.length(), i - 1 /* i - 1 instead of i because of & */);
-
- // We don't have to check that keyId is the last parameter, because the long conversion would
- // fail anyway if there's any trailing data.
- try {
- long keyId = Long.parseLong(sigAndKeyId.substring(i + KEY_ID_PARAM_NAME.length()));
- verify(tbsData, keyId, Base64.urlSafeDecode(sig));
- } catch (NumberFormatException ex) {
- throw new GeneralSecurityException("key_id must be a long");
- }
- }
-
- private void verify(final byte[] tbs, long keyId, final byte[] signature)
- throws GeneralSecurityException {
- boolean foundKeyId = false;
- for (VerifyingPublicKeysProvider provider : verifyingPublicKeysProviders) {
- Map<Long, ECPublicKey> publicKeys = provider.get();
- if (publicKeys.containsKey(keyId)) {
- foundKeyId = true;
- ECPublicKey publicKey = publicKeys.get(keyId);
- EcdsaVerifyJce verifier = new EcdsaVerifyJce(publicKey, HashType.SHA256, EcdsaEncoding.DER);
- verifier.verify(signature, tbs);
- }
- }
- if (!foundKeyId) {
- throw new GeneralSecurityException("cannot find verifying key with key id: " + keyId);
- }
- }
-
- /** Builder for RewardedAdsVerifier. */
- public static class Builder {
- private final List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders =
- new ArrayList<>();
-
- public Builder() {}
-
- /**
- * Fetches verifying public keys of the sender using {@link KeysDownloader}.
- *
- * <p>This is the preferred method of specifying the verifying public keys.
- */
- public Builder fetchVerifyingPublicKeysWith(final KeysDownloader downloader)
- throws GeneralSecurityException {
- this.verifyingPublicKeysProviders.add(
- new VerifyingPublicKeysProvider() {
- @Override
- public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
- try {
- return parsePublicKeysJson(downloader.download());
- } catch (IOException e) {
- throw new GeneralSecurityException("Failed to fetch keys!", e);
- }
- }
- });
- return this;
- }
-
- /**
- * Sets the trusted verifying public keys of the sender.
- *
- * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
- * sender, prefer calling {@link #fetchVerifyingPublicKeysWith} passing it an instance of {@link
- * KeysDownloader}. It will take care of fetching fresh keys and caching in memory. Only use
- * this method if you can't use {@link #fetchVerifyingPublicKeysWith} and be aware you will need
- * to handle key rotations yourself.
- *
- * <p>The given string is a JSON object formatted like the following:
- *
- * <pre>
- * {
- * "keys": [
- * {
- * keyId: 1916455855,
- * pem: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUaWMKcBHWdhUE+DncSIHhFCLLEln\nUs0LB9oanZ4K/FNICIM8ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw==\n-----END PUBLIC KEY-----"
- * base64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUaWMKcBHWdhUE+DncSIHhFCLLElnUs0LB9oanZ4K/FNICIM8ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw=="
- * },
- * {
- * keyId: 3901585526,
- * pem: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtxg2BsK/fllIeADtLspezS6YfHFWXZ8tiJncm8LDBa/NxEC84akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw==\n-----END PUBLIC KEY-----"
- * base64: "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtxg2BsK/fllIeADtLspezS6YfHFWXZ8tiJncm8LDBa/NxEC84akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw=="
- * },
- * ],
- * }
- * </pre>
- *
- * <p>Each public key will be a base64 (no wrapping, padded) version of the key encoded in ASN.1
- * type SubjectPublicKeyInfo defined in the X.509 standard.
- */
- public Builder setVerifyingPublicKeys(final String publicKeysJson)
- throws GeneralSecurityException {
- this.verifyingPublicKeysProviders.add(
- new VerifyingPublicKeysProvider() {
- @Override
- public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
- return parsePublicKeysJson(publicKeysJson);
- }
- });
- return this;
- }
-
- /**
- * Adds a verifying public key of the sender.
- *
- * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
- * sender, prefer calling {@link #fetchVerifyingPublicKeysWith} passing it an instance of {@link
- * KeysDownloader}. It will take care of fetching fresh keys and caching in memory. Only use
- * this method if you can't use {@link #fetchVerifyingPublicKeysWith} and be aware you will need
- * to handle Google key rotations yourself.
- *
- * <p>The public key is a base64 (no wrapping, padded) version of the key encoded in ASN.1 type
- * SubjectPublicKeyInfo defined in the X.509 standard.
- *
- * <p>Multiple keys may be added. This utility will then verify any message signed with any of
- * the private keys corresponding to the public keys added. Adding multiple keys is useful for
- * handling key rotation.
- */
- public Builder addVerifyingPublicKey(final long keyId, final String val)
- throws GeneralSecurityException {
- this.verifyingPublicKeysProviders.add(
- new VerifyingPublicKeysProvider() {
- @Override
- public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
- return Collections.singletonMap(
- keyId, EllipticCurves.getEcPublicKey(Base64.decode(val)));
- }
- });
- return this;
- }
-
- /**
- * Adds a verifying public key of the sender.
- *
- * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
- * sender, prefer calling {@link #fetchVerifyingPublicKeysWith} passing it an instance of {@link
- * KeysDownloader}. It will take care of fetching fresh keys and caching in memory. Only use
- * this method if you can't use {@link #fetchVerifyingPublicKeysWith} and be aware you will need
- * to handle Google key rotations yourself.
- */
- public Builder addVerifyingPublicKey(final long keyId, final ECPublicKey val)
- throws GeneralSecurityException {
- this.verifyingPublicKeysProviders.add(
- new VerifyingPublicKeysProvider() {
- @Override
- public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
- return Collections.singletonMap(keyId, val);
- }
- });
- return this;
- }
-
- public RewardedAdsVerifier build() throws GeneralSecurityException {
- return new RewardedAdsVerifier(this);
- }
- }
-
- private static Map<Long, ECPublicKey> parsePublicKeysJson(String publicKeysJson)
- throws GeneralSecurityException {
- Map<Long, ECPublicKey> publicKeys = new HashMap<>();
- try {
- JsonArray keys =
- JsonParser.parseString(publicKeysJson).getAsJsonObject().get("keys").getAsJsonArray();
- for (int i = 0; i < keys.size(); i++) {
- JsonObject key = keys.get(i).getAsJsonObject();
- publicKeys.put(
- key.get("keyId").getAsLong(),
- EllipticCurves.getEcPublicKey(Base64.decode(key.get("base64").getAsString())));
- }
- } catch (JsonParseException | IllegalStateException e) {
- throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
- }
- if (publicKeys.isEmpty()) {
- throw new GeneralSecurityException("no trusted keys are available for this protocol version");
- }
- return publicKeys;
- }
-
- private interface VerifyingPublicKeysProvider {
- Map<Long, ECPublicKey> get() throws GeneralSecurityException;
- }
-}
diff --git a/apps/rewardedads/src/test/BUILD.bazel b/apps/rewardedads/src/test/BUILD.bazel
deleted file mode 100644
index 244de24..0000000
--- a/apps/rewardedads/src/test/BUILD.bazel
+++ /dev/null
@@ -1,35 +0,0 @@
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Tests
-
-java_library(
- name = "generator_test",
- testonly = 1,
- srcs = glob([
- "**/*.java",
- ]),
- deps = [
- "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier",
- "@maven//:com_google_code_gson_gson",
- "@maven//:com_google_http_client_google_http_client",
- "@maven//:junit_junit",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:enums",
- "@tink_java//src/main/java/com/google/crypto/tink/util:keys_downloader",
- ],
-)
-
-gen_java_test_rules(
- test_files = glob([
- "**/*Test.java",
- ]),
- deps = [
- ":generator_test",
- ],
-)
diff --git a/apps/rewardedads/src/test/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifierTest.java b/apps/rewardedads/src/test/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifierTest.java
deleted file mode 100644
index f0e636e..0000000
--- a/apps/rewardedads/src/test/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifierTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.rewardedads;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.google.api.client.testing.http.MockHttpTransport;
-import com.google.api.client.testing.http.MockLowLevelHttpResponse;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaSignJce;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.crypto.tink.subtle.Enums.HashType;
-import com.google.crypto.tink.util.KeysDownloader;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.net.URI;
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code RewardedAdsVerifier}. */
-@RunWith(JUnit4.class)
-public class RewardedAdsVerifierTest {
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
- /** Sample Google provided JSON with its public signing keys. */
- private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
- "{\"keys\":[{"
- + "\"keyId\":1234,"
- + "\"base64\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQt"
- + "lxizmLFynwHcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\""
- + "}]}";
-
- /**
- * Sample Google private signing key.
- *
- * <p>Corresponds to private key of the key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
- */
- private static final String GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64 =
- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZj/Dldxz8fvKVF5O"
- + "TeAtK6tY3G1McmvhMppe6ayW6GahRANCAAQ9icfBLy56BYB7BC2XGLOYsXKfAdzF"
- + "FPU8rTtwMDr8LixetUjVLNkJTHxTxLQuMytPqKt39Vbtt7czPq3+yu1F";
-
- // must match the value in GOOGLE_VERIFYING_PUBLIC_KEYS_JSON
- private static final long KEY_ID = 1234;
- private static final String REWARD_HOST_AND_PATH = "https://publisher.com/blah?";
- private static final String REWARD_QUERY = "foo1=bar1&foo2=bar2";
- private static final String REWARD_URL = REWARD_HOST_AND_PATH + REWARD_QUERY;
-
- private static final String ALTERNATE_PUBLIC_SIGNING_KEY =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU8E6JppGKFG40r5dDU1idHRN52NuwsemFzXZh1oUqh3bGUPgPioH+RoW"
- + "nmVSUQz1WfM2426w9f0GADuXzpUkcw==";
-
- private static String signUrl(String rewardUrl, String privateKey, long keyId) throws Exception {
- EcdsaSignJce signer =
- new EcdsaSignJce(
- EllipticCurves.getEcPrivateKey(Base64.decode(privateKey)),
- HashType.SHA256,
- EcdsaEncoding.DER);
- String queryString = new URI(rewardUrl).getQuery();
- return buildUrl(rewardUrl, signer.sign(queryString.getBytes(UTF_8)), keyId);
- }
-
- private static String buildUrl(String rewardUrl, byte[] sig, long keyId) {
- return new StringBuilder(rewardUrl)
- .append("&")
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .append(Base64.urlSafeEncode(sig))
- .append("&")
- .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
- .append(keyId)
- .toString();
- }
-
- @Test
- public void testShouldVerify() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID));
- }
-
- @Test
- public void testShouldVerifyIfKeyIdIsLargerThanMaxInt() throws Exception {
- long keyId = Integer.MAX_VALUE + 1;
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-
- trustedKeysJson.getAsJsonArray("keys").get(0).getAsJsonObject().addProperty("keyId", keyId);
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(trustedKeysJson.toString())
- .build();
- verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, keyId));
- }
-
- @Test
- public void testShouldVerifyWithEncodedUrl() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
-
- String rewardUrl = "https://publisher.com/path?foo=hello%20world&bar=user%40gmail.com";
- String decodedQueryString = "foo=hello world&bar=user@gmail.com";
- EcdsaSignJce signer =
- new EcdsaSignJce(
- EllipticCurves.getEcPrivateKey(Base64.decode(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)),
- HashType.SHA256,
- EcdsaEncoding.DER);
- byte[] signature = signer.sign(decodedQueryString.getBytes(UTF_8));
- String signedUrl = buildUrl(rewardUrl, signature, KEY_ID);
- verifier.verify(signedUrl);
- }
-
- @Test
- public void testShouldDecryptV1WhenFetchingSenderVerifyingKeys() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .fetchVerifyingPublicKeysWith(
- new KeysDownloader.Builder()
- .setHttpTransport(
- new MockHttpTransport.Builder()
- .setLowLevelHttpResponse(
- new MockLowLevelHttpResponse()
- .setContent(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON))
- .build())
- .setUrl("https://someUrl" /* unused */)
- .build())
- .build();
-
- verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID));
- }
-
- @Test
- public void testShouldFailIfVerifyingWithDifferentKey() throws Exception {
- JsonObject trustedKeysJson =
- JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
- trustedKeysJson
- .getAsJsonArray("keys")
- .get(0)
- .getAsJsonObject()
- .addProperty("base64", ALTERNATE_PUBLIC_SIGNING_KEY);
-
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(trustedKeysJson.toString())
- .build();
-
- try {
- verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("Invalid signature", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailIfKeyIdNotFound() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
-
- try {
- verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID + 1));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertTrue(e.getMessage().contains("cannot find verifying key with key id"));
- }
- }
-
- @Test
- public void testShouldFailIfSignedMessageWasChanged() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- byte[] validSignedUrl =
- signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID).getBytes(UTF_8);
- for (int i = REWARD_HOST_AND_PATH.length(); i < REWARD_URL.length(); i++) {
- byte[] modifiedUrl = Arrays.copyOf(validSignedUrl, validSignedUrl.length);
- modifiedUrl[i] = (byte) (modifiedUrl[i] ^ 0xff);
- try {
- verifier.verify(new String(modifiedUrl, UTF_8));
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- assertEquals("Invalid signature", e.getMessage());
- }
- }
- }
-
- @Test
- public void testShouldFailIfSignatureWasChanged() throws Exception {
- EcdsaSignJce signer =
- new EcdsaSignJce(
- EllipticCurves.getEcPrivateKey(Base64.decode(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)),
- HashType.SHA256,
- EcdsaEncoding.DER);
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
-
- byte[] validSig = signer.sign(REWARD_URL.getBytes(UTF_8));
- for (int i = 0; i < validSig.length; i++) {
- byte[] modifiedSig = Arrays.copyOf(validSig, validSig.length);
- modifiedSig[i] = (byte) (modifiedSig[i] ^ 0xff);
- String modifiedUrl = buildUrl(REWARD_URL, modifiedSig, KEY_ID);
- try {
- verifier.verify(modifiedUrl);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException e) {
- // Expected.
- System.out.println(e);
- }
- }
- }
-
- @Test
- public void testShouldFailWithoutSignature() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(REWARD_URL);
- } catch (GeneralSecurityException e) {
- assertEquals("signature and key id must be the last two query parameters", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailWithoutKeyId() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(
- new StringBuilder(REWARD_URL)
- .append("&")
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .append("foo")
- .toString());
- } catch (GeneralSecurityException e) {
- assertEquals("signature and key id must be the last two query parameters", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(
- new StringBuilder(REWARD_HOST_AND_PATH)
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .toString());
- } catch (GeneralSecurityException e) {
- assertEquals("signature and key id must be the last two query parameters", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters2() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(
- new StringBuilder(REWARD_HOST_AND_PATH)
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .append("foo")
- .append("&")
- .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
- .append("123")
- .append("bar=baz")
- .toString());
- } catch (GeneralSecurityException e) {
- assertEquals("signature and key id must be the last two query parameters", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters3() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(
- new StringBuilder(REWARD_URL)
- .append("&")
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .append("foo")
- .append("&")
- .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
- .append("123")
- .append("&bar=baz") // this would be interpreted as part of the key ID
- .toString());
- } catch (GeneralSecurityException e) {
- assertEquals("key_id must be a long", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailWithSignatureAndKeyIdNoAmpersand() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(
- new StringBuilder(REWARD_URL)
- .append("&")
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .append("foo")
- .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
- .append("123")
- .toString());
- } catch (GeneralSecurityException e) {
- assertEquals("signature and key id must be the last two query parameters", e.getMessage());
- }
- }
-
- @Test
- public void testShouldFailWithKeyIdNotLong() throws Exception {
- RewardedAdsVerifier verifier =
- new RewardedAdsVerifier.Builder()
- .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
- .build();
- try {
- verifier.verify(
- new StringBuilder(REWARD_URL)
- .append("&")
- .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
- .append("foo")
- .append("&")
- .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
- .append("not_long")
- .toString());
- } catch (GeneralSecurityException e) {
- assertEquals("key_id must be a long", e.getMessage());
- }
- }
-}
diff --git a/apps/webpush/BUILD.bazel b/apps/webpush/BUILD.bazel
deleted file mode 100644
index 7260048..0000000
--- a/apps/webpush/BUILD.bazel
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-gen_maven_jar_rules(
- name = "maven",
- doctitle = "Tink Cryptography API for Message Encryption for Web Push (RFC 8291)",
- manifest_lines = [
- "Automatic-Module-Name: com.google.crypto.tink.apps.webpush",
- ],
- root_packages = ["com.google.crypto.tink.apps.webpush"],
- deps = [
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_constants",
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_decrypt",
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_encrypt",
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_util",
- ],
-)
diff --git a/apps/webpush/README.md b/apps/webpush/README.md
deleted file mode 100644
index 1018f4d..0000000
--- a/apps/webpush/README.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# Message Encryption for Web Push
-
-This Tink app is an implementation of [RFC 8291 - Message Encryption for Web
-Push](https://tools.ietf.org/html/rfc8291).
-
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-webpush/1.7.0).
-
-## Installation
-
-To add a dependency using Maven:
-
-```xml
-<dependency>
- <groupId>com.google.crypto.tink</groupId>
- <artifactId>apps-webpush</artifactId>
- <version>1.7.0</version>
-</dependency>
-```
-
-To add a dependency using Gradle:
-
-```
-dependencies {
- implementation 'com.google.crypto.tink:apps-webpush:1.7.0'
-}
-```
-
-## Encryption
-
-```java
-import com.google.crypto.tink.HybridEncrypt;
-import java.security.interfaces.ECPublicKey;
-
-ECPublicKey reicipientPublicKey = ...;
-byte[] authSecret = ...;
-HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(recipientPublicKey)
- .build();
-byte[] plaintext = ...;
-byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo, must be null */);
-```
-
-## Decryption
-
-```java
-import com.google.crypto.tink.HybridDecrypt;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-
-ECPrivateKey recipientPrivateKey = ...;
-ECPublicKey recipientPublicKey = ...;
-HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(recipientPublicKey)
- .withRecipientPrivateKey(recipientPrivateKey)
- .build();
-byte[] ciphertext = ...;
-byte[] plaintext = hybridDecrypt.decrypt(ciphertext, /* contextInfo, must be null */);
-```
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel
deleted file mode 100644
index 5d2afbb..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel
+++ /dev/null
@@ -1,44 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-java_library(
- name = "web_push_hybrid_decrypt",
- srcs = ["WebPushHybridDecrypt.java"],
- deps = [
- ":web_push_constants",
- ":web_push_util",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
- ],
-)
-
-java_library(
- name = "web_push_util",
- srcs = ["WebPushUtil.java"],
- deps = [
- ":web_push_constants",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:bytes",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:hkdf",
- ],
-)
-
-java_library(
- name = "web_push_constants",
- srcs = ["WebPushConstants.java"],
- deps = ["@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves"],
-)
-
-java_library(
- name = "web_push_hybrid_encrypt",
- srcs = ["WebPushHybridEncrypt.java"],
- deps = [
- ":web_push_constants",
- ":web_push_util",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
- ],
-)
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java
deleted file mode 100644
index c864021..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.subtle.EllipticCurves;
-import java.nio.charset.Charset;
-
-/** Various constants. */
-final class WebPushConstants {
- static final Charset UTF_8 = Charset.forName("UTF-8");
- static final int AUTH_SECRET_SIZE = 16;
- static final int IKM_SIZE = 32;
- static final int CEK_KEY_SIZE = 16;
- static final int NONCE_SIZE = 12;
- static final byte[] IKM_INFO =
- new byte[] {'W', 'e', 'b', 'P', 'u', 's', 'h', ':', ' ', 'i', 'n', 'f', 'o', (byte) 0};
- static final byte[] CEK_INFO =
- new byte[] {
- 'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ',
- 'a', 'e', 's', '1', '2', '8', 'g', 'c', 'm', (byte) 0
- };
- static final byte[] NONCE_INFO =
- new byte[] {
- 'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ',
- 'n', 'o', 'n', 'c', 'e', (byte) 0
- };
-
- static final int SALT_SIZE = 16;
- static final int RECORD_SIZE_LEN = 4;
- static final int PUBLIC_KEY_SIZE_LEN = 1;
- static final int PUBLIC_KEY_SIZE = 65;
- // * salt: 16
- // * record size: 4
- // * public key size: 1
- // * uncompressed public key: 65
- // * total : 86
- static final int CONTENT_CODING_HEADER_SIZE =
- SALT_SIZE + RECORD_SIZE_LEN + PUBLIC_KEY_SIZE_LEN + PUBLIC_KEY_SIZE;
-
- // the byte 0x2 separating the payload and the padding
- static final byte PADDING_DELIMITER_BYTE = (byte) 2;
- static final int PADDING_DELIMETER_SIZE = 1;
- static final int DEFAULT_PADDING_SIZE = 0;
- static final int TAG_SIZE = 16;
- // * content coding header: 86
- // * padding delimeter: 1
- // * AES-GCM tag size: 16
- // * Total: 103
- static final int CIPHERTEXT_OVERHEAD =
- CONTENT_CODING_HEADER_SIZE + PADDING_DELIMETER_SIZE + DEFAULT_PADDING_SIZE + TAG_SIZE;
-
- static final int MAX_CIPHERTEXT_SIZE = 4096;
-
- static final String HMAC_SHA256 = "HMACSHA256";
- static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT =
- EllipticCurves.PointFormatType.UNCOMPRESSED;
- static final EllipticCurves.CurveType NIST_P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256;
-
- private WebPushConstants() {}
-}
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecrypt.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecrypt.java
deleted file mode 100644
index 2062c54..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecrypt.java
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EngineFactory;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * A {@link HybridDecrypt} implementation for the hybrid encryption used in <a
- * href="https://tools.ietf.org/html/rfc8291">RFC 8291 - Web Push Message Encryption</a>.
- *
- * <h3>Ciphertext format</h3>
- *
- * <p>When used with <a href="https://tools.ietf.org/html/rfc8291#section-4">AES128-GCM content
- * encoding</a>, which is the only content encoding supported in this implementation, the ciphertext
- * is formatted according to RFC 8188 section 2, and looks as follows
- *
- * <pre>
- * // NOLINTNEXTLINE
- * +-----------+----------------+------------------+---------------------------------------------------
- * | salt (16) | recordsize (4) | publickeylen (1) | publickey (publickeylen) | aes128-gcm-ciphertext |
- * +-----------+----------------+------------------+---------------------------------------------------
- * </pre>
- *
- * <p>RFC 8188 divides messages into records which are encrypted independently. Web Push messages
- * cannot be longer than 3993 bytes, and are always encrypted in a single record with default size
- * of 4096 bytes. {@code aes128-gcm-ciphertext} is the encryption of the message padded with a
- * single byte of value {@code 0x02} (which indicates that this is the last and only record).
- *
- * <h3>Usage</h3>
- *
- * <pre>{@code
- * import com.google.crypto.tink.HybridDecrypt;
- * import com.google.crypto.tink.HybridEncrypt;
- * import java.security.interfaces.ECPrivateKey;
- * import java.security.interfaces.ECPublicKey;
- *
- * // Encryption.
- * ECPublicKey reicipientPublicKey = ...;
- * byte[] authSecret = ...;
- * HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder()
- * .withAuthSecret(authSecret)
- * .withRecipientPublicKey(recipientPublicKey)
- * .build();
- * byte[] plaintext = ...;
- * byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null);
- *
- * // Decryption.
- * ECPrivateKey recipientPrivateKey = ...;
- * HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder()
- * .withAuthSecret(authSecret)
- * .withRecipientPublicKey(recipientPublicKey)
- * .withRecipientPrivateKey(recipientPrivateKey)
- * .build();
- * byte[] plaintext = hybridDecrypt.decrypt(ciphertext, null);
- * }</pre>
- *
- * @since 1.1.0
- */
-public final class WebPushHybridDecrypt implements HybridDecrypt {
- private final ECPrivateKey recipientPrivateKey;
- private final byte[] recipientPublicKey;
- private final byte[] authSecret;
- private final int recordSize;
-
- private WebPushHybridDecrypt(Builder builder) throws GeneralSecurityException {
- if (builder.recipientPrivateKey == null) {
- throw new IllegalArgumentException(
- "must set recipient's private key with Builder.withRecipientPrivateKey");
- }
- this.recipientPrivateKey = builder.recipientPrivateKey;
-
- if (builder.recipientPublicKey == null
- || builder.recipientPublicKey.length != WebPushConstants.PUBLIC_KEY_SIZE) {
- throw new IllegalArgumentException(
- "recipient public key must have " + WebPushConstants.PUBLIC_KEY_SIZE + " bytes");
- }
- this.recipientPublicKey = builder.recipientPublicKey;
-
- if (builder.authSecret == null) {
- throw new IllegalArgumentException("must set auth secret with Builder.withAuthSecret");
- }
- if (builder.authSecret.length != WebPushConstants.AUTH_SECRET_SIZE) {
- throw new IllegalArgumentException(
- "auth secret must have " + WebPushConstants.AUTH_SECRET_SIZE + " bytes");
- }
- this.authSecret = builder.authSecret;
-
- if (builder.recordSize < WebPushConstants.CIPHERTEXT_OVERHEAD
- || builder.recordSize > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
- throw new IllegalArgumentException(
- String.format(
- "invalid record size (%s); must be a number between [%s, %s]",
- builder.recordSize,
- WebPushConstants.CIPHERTEXT_OVERHEAD,
- WebPushConstants.MAX_CIPHERTEXT_SIZE));
- }
- this.recordSize = builder.recordSize;
- }
-
- /**
- * Builder for {@link WebPushHybridDecrypt}.
- *
- * @since 1.1.0
- */
- public static final class Builder {
- private ECPrivateKey recipientPrivateKey = null;
- private byte[] recipientPublicKey = null;
- private byte[] authSecret = null;
- private int recordSize = WebPushConstants.MAX_CIPHERTEXT_SIZE;
-
- public Builder() {}
-
- /**
- * Sets the record size.
- *
- * <p>If set, this value must match the record size set with {@link
- * WebPushHybridEncrypt.Builder#withRecordSize}.
- *
- * <p>If not set, a record size of 4096 bytes is used. This value should work for most users.
- */
- public Builder withRecordSize(int val) {
- recordSize = val;
- return this;
- }
-
- /** Sets the authentication secret. */
- public Builder withAuthSecret(final byte[] val) {
- authSecret = val.clone();
- return this;
- }
-
- /** Sets the public key of the recipient. */
- public Builder withRecipientPublicKey(ECPublicKey val) throws GeneralSecurityException {
- recipientPublicKey =
- EllipticCurves.pointEncode(
- WebPushConstants.NIST_P256_CURVE_TYPE,
- WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
- val.getW());
- return this;
- }
-
- /**
- * Sets the public key of the recipient.
- *
- * <p>The public key must be formatted as an uncompressed point format, i.e., it has {@code 65}
- * bytes and the first byte must be {@code 0x04}.
- */
- public Builder withRecipientPublicKey(final byte[] val) {
- recipientPublicKey = val.clone();
- return this;
- }
-
- /** Sets the private key of the recipient. */
- public Builder withRecipientPrivateKey(ECPrivateKey val) throws GeneralSecurityException {
- recipientPrivateKey = val;
- return this;
- }
-
- /**
- * Sets the private key of the recipient.
- *
- * <p>The private key is the serialized bytes of the BigInteger returned by
- * {@link ECPrivateKey#getS()}.
- */
- public Builder withRecipientPrivateKey(final byte[] val) throws GeneralSecurityException {
- recipientPrivateKey =
- EllipticCurves.getEcPrivateKey(WebPushConstants.NIST_P256_CURVE_TYPE, val);
- return this;
- }
-
- public WebPushHybridDecrypt build() throws GeneralSecurityException {
- return new WebPushHybridDecrypt(this);
- }
- }
-
- @Override
- public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo /* unused */)
- throws GeneralSecurityException {
- if (contextInfo != null) {
- throw new GeneralSecurityException("contextInfo must be null because it is unused");
- }
-
- if (ciphertext.length < WebPushConstants.CIPHERTEXT_OVERHEAD) {
- throw new GeneralSecurityException("ciphertext too short");
- }
-
- // A push service is not required to support more than 4096 octets of
- // payload body. See https://tools.ietf.org/html/rfc8291#section-4.0.
- if (ciphertext.length > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
- throw new GeneralSecurityException("ciphertext too long");
- }
-
- // Unpacking.
- ByteBuffer record = ByteBuffer.wrap(ciphertext);
- byte[] salt = new byte[WebPushConstants.SALT_SIZE];
- record.get(salt);
-
- int recordSize = record.getInt();
- if (recordSize != this.recordSize
- || recordSize < ciphertext.length
- || recordSize > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
- throw new GeneralSecurityException("invalid record size: " + recordSize);
- }
-
- int publicKeySize = (int) record.get();
- if (publicKeySize != WebPushConstants.PUBLIC_KEY_SIZE) {
- throw new GeneralSecurityException("invalid ephemeral public key size: " + publicKeySize);
- }
-
- byte[] asPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE];
- record.get(asPublicKey);
- ECPoint asPublicPoint =
- EllipticCurves.pointDecode(
- WebPushConstants.NIST_P256_CURVE_TYPE,
- WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
- asPublicKey);
-
- byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE];
- record.get(payload);
-
- // See https://tools.ietf.org/html/rfc8291#section-3.4.
- byte[] ecdhSecret = EllipticCurves.computeSharedSecret(recipientPrivateKey, asPublicPoint);
- byte[] ikm = WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, asPublicKey);
- byte[] cek = WebPushUtil.computeCek(ikm, salt);
- byte[] nonce = WebPushUtil.computeNonce(ikm, salt);
-
- return decrypt(cek, nonce, payload);
- }
-
- private byte[] decrypt(final byte[] key, final byte[] nonce, final byte[] ciphertext)
- throws GeneralSecurityException {
- Cipher cipher = EngineFactory.CIPHER.getInstance("AES/GCM/NoPadding");
- GCMParameterSpec params = new GCMParameterSpec(8 * WebPushConstants.TAG_SIZE, nonce);
- cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), params);
- byte[] plaintext = cipher.doFinal(ciphertext);
- if (plaintext.length == 0) {
- throw new GeneralSecurityException("decryption failed");
- }
- // Remove zero paddings.
- int index = plaintext.length - 1;
- while (index > 0) {
- if (plaintext[index] != 0) {
- break;
- }
- index--;
- }
-
- if (plaintext[index] != WebPushConstants.PADDING_DELIMITER_BYTE) {
- throw new GeneralSecurityException("decryption failed");
- }
- return Arrays.copyOf(plaintext, index);
- }
-}
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncrypt.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncrypt.java
deleted file mode 100644
index dfb7555..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncrypt.java
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EngineFactory;
-import com.google.crypto.tink.subtle.Random;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-import javax.crypto.Cipher;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * A {@link HybridEncrypt} implementation for the hybrid encryption used in <a
- * href="https://tools.ietf.org/html/rfc8291">RFC 8291 - Web Push Message Encryption</a>.
- *
- * <h3>Ciphertext format</h3>
- *
- * <p>When used with <a href="https://tools.ietf.org/html/rfc8291#section-4">AES128-GCM content
- * encoding</a>, which is the only content encoding supported in this implementation, the ciphertext
- * is formatted according to RFC 8188 section 2, and looks as follows
- *
- * <pre>
- * // NOLINTNEXTLINE
- * +-----------+----------------+------------------+---------------------------------------------------
- * | salt (16) | recordsize (4) | publickeylen (1) | publickey (publickeylen) | aes128-gcm-ciphertext |
- * +-----------+----------------+------------------+---------------------------------------------------
- * </pre>
- *
- * <p>RFC 8188 divides messages into records which are encrypted independently. Web Push messages
- * cannot be longer than 3993 bytes, and are always encrypted in a single record with default size
- * of 4096 bytes. {@code aes128-gcm-ciphertext} is the encryption of the message padded with a
- * single byte of value {@code 0x02} (which indicates that this is the last and only record).
- *
- * <h3>Usage</h3>
- *
- * <pre>{@code
- * import com.google.crypto.tink.HybridDecrypt;
- * import com.google.crypto.tink.HybridEncrypt;
- * import java.security.interfaces.ECPrivateKey;
- * import java.security.interfaces.ECPublicKey;
- *
- * // Encryption.
- * ECPublicKey reicipientPublicKey = ...;
- * byte[] authSecret = ...;
- * HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder()
- * .withAuthSecret(authSecret)
- * .withRecipientPublicKey(recipientPublicKey)
- * .build();
- * byte[] plaintext = ...;
- * byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null);
- *
- * // Decryption.
- * ECPrivateKey recipientPrivateKey = ...;
- * HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder()
- * .withAuthSecret(authSecret)
- * .withRecipientPublicKey(recipientPublicKey)
- * .withRecipientPrivateKey(recipientPrivateKey)
- * .build();
- * byte[] plaintext = hybridDecrypt.decrypt(ciphertext, null);
- * }</pre>
- *
- * @since 1.1.0
- */
-public final class WebPushHybridEncrypt implements HybridEncrypt {
- private final byte[] recipientPublicKey;
- private final byte[] authSecret;
- private final ECPoint recipientPublicPoint;
- private final int recordSize;
- private final int paddingSize;
-
- private WebPushHybridEncrypt(Builder builder) throws GeneralSecurityException {
- if (builder.recipientPublicKey == null || builder.recipientPublicPoint == null) {
- throw new IllegalArgumentException(
- "must set recipient's public key with Builder.withRecipientPublicKey");
- }
- this.recipientPublicKey = builder.recipientPublicKey;
- this.recipientPublicPoint = builder.recipientPublicPoint;
-
- if (builder.authSecret == null) {
- throw new IllegalArgumentException("must set auth secret with Builder.withAuthSecret");
- }
- if (builder.authSecret.length != WebPushConstants.AUTH_SECRET_SIZE) {
- throw new IllegalArgumentException(
- "auth secret must have " + WebPushConstants.AUTH_SECRET_SIZE + " bytes");
- }
- this.authSecret = builder.authSecret;
- this.recordSize = builder.recordSize;
- this.paddingSize = builder.paddingSize;
- }
-
- /**
- * Builder for {@link WebPushHybridEncrypt}.
- *
- * @since 1.1.0
- */
- public static final class Builder {
- private byte[] recipientPublicKey = null;
- private ECPoint recipientPublicPoint = null;
- private byte[] authSecret = null;
- private int recordSize = WebPushConstants.MAX_CIPHERTEXT_SIZE;
- private int paddingSize = WebPushConstants.DEFAULT_PADDING_SIZE;
-
- public Builder() {}
-
- /**
- * Sets the record size.
- *
- * <p>If set, this value must match the record size set with {@link
- * WebPushHybridEncrypt.Builder#withRecordSize}.
- *
- * <p>If not set, a record size of 4096 bytes is used. This value should work for most users.
- */
- public Builder withRecordSize(int val) {
- if (val < WebPushConstants.CIPHERTEXT_OVERHEAD
- || val > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
- throw new IllegalArgumentException(
- String.format(
- "invalid record size (%s); must be a number between [%s, %s]",
- val, WebPushConstants.CIPHERTEXT_OVERHEAD, WebPushConstants.MAX_CIPHERTEXT_SIZE));
- }
-
- recordSize = val;
- return this;
- }
-
- /**
- * Sets the padding size which is default to 0.
- *
- * <p>The padding size cannot be larger than
- */
- public Builder withPaddingSize(int val) {
- if (val < 0
- || val > WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD) {
- throw new IllegalArgumentException(
- String.format(
- "invalid padding size (%s); must be a number between [%s, %s]",
- val,
- 0,
- WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD));
- }
-
- paddingSize = val;
- return this;
- }
-
- /** Sets the authentication secret. */
- public Builder withAuthSecret(final byte[] val) {
- authSecret = val.clone();
- return this;
- }
-
- /** Sets the public key of the recipient. */
- public Builder withRecipientPublicKey(ECPublicKey val) throws GeneralSecurityException {
- recipientPublicPoint = val.getW();
- recipientPublicKey =
- EllipticCurves.pointEncode(
- WebPushConstants.NIST_P256_CURVE_TYPE,
- WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
- val.getW());
-
- return this;
- }
-
- /**
- * Sets the public key of the recipient.
- *
- * <p>The public key must be formatted as an uncompressed point format, i.e., it has {@code 65}
- * bytes and the first byte must be {@code 0x04}.
- */
- public Builder withRecipientPublicKey(final byte[] val) throws GeneralSecurityException {
- recipientPublicKey = val.clone();
- recipientPublicPoint =
- EllipticCurves.pointDecode(
- WebPushConstants.NIST_P256_CURVE_TYPE,
- WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
- recipientPublicKey);
- return this;
- }
-
- public WebPushHybridEncrypt build() throws GeneralSecurityException {
- return new WebPushHybridEncrypt(this);
- }
- }
-
- @Override
- public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo /* unused */)
- throws GeneralSecurityException {
- if (contextInfo != null) {
- throw new GeneralSecurityException("contextInfo must be null because it is unused");
- }
-
- if (plaintext.length > recordSize - paddingSize - WebPushConstants.CIPHERTEXT_OVERHEAD) {
- throw new GeneralSecurityException(
- String.format(
- "plaintext too long; with record size = %d and padding size = %d, plaintext cannot"
- + " be longer than %d",
- recordSize,
- paddingSize,
- recordSize - paddingSize - WebPushConstants.CIPHERTEXT_OVERHEAD));
- }
-
- // See https://tools.ietf.org/html/rfc8291#section-3.4.
- KeyPair keyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey ephemeralPrivateKey = (ECPrivateKey) keyPair.getPrivate();
- ECPublicKey ephemeralPublicKey = (ECPublicKey) keyPair.getPublic();
- byte[] ecdhSecret =
- EllipticCurves.computeSharedSecret(ephemeralPrivateKey, recipientPublicPoint);
- byte[] ephemeralPublicKeyBytes =
- EllipticCurves.pointEncode(
- WebPushConstants.NIST_P256_CURVE_TYPE,
- WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
- ephemeralPublicKey.getW());
- byte[] ikm =
- WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, ephemeralPublicKeyBytes);
- byte[] salt = Random.randBytes(WebPushConstants.SALT_SIZE);
- byte[] cek = WebPushUtil.computeCek(ikm, salt);
- byte[] nonce = WebPushUtil.computeNonce(ikm, salt);
- return ByteBuffer.allocate(
- WebPushConstants.CIPHERTEXT_OVERHEAD + plaintext.length + paddingSize)
- .put(salt)
- .putInt(recordSize)
- .put((byte) WebPushConstants.PUBLIC_KEY_SIZE)
- .put(ephemeralPublicKeyBytes)
- .put(encrypt(cek, nonce, plaintext))
- .array();
- }
-
- private byte[] encrypt(final byte[] key, final byte[] nonce, final byte[] plaintext)
- throws GeneralSecurityException {
- Cipher cipher = EngineFactory.CIPHER.getInstance("AES/GCM/NoPadding");
- GCMParameterSpec params = new GCMParameterSpec(8 * WebPushConstants.TAG_SIZE, nonce);
- cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), params);
- byte[] paddedPlaintext = new byte[plaintext.length + 1 + paddingSize];
- paddedPlaintext[plaintext.length] = WebPushConstants.PADDING_DELIMITER_BYTE;
- System.arraycopy(plaintext, 0, paddedPlaintext, 0, plaintext.length);
- return cipher.doFinal(paddedPlaintext);
- }
-}
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java
deleted file mode 100644
index c2fc72e..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.Hkdf;
-import java.security.GeneralSecurityException;
-
-/** Various helpers. */
-final class WebPushUtil {
- public static byte[] computeIkm(
- final byte[] ecdhSecret,
- final byte[] authSecret,
- final byte[] uaPublic,
- final byte[] asPublic)
- throws GeneralSecurityException {
- byte[] keyInfo = Bytes.concat(WebPushConstants.IKM_INFO, uaPublic, asPublic);
- return Hkdf.computeHkdf(
- WebPushConstants.HMAC_SHA256,
- ecdhSecret /* ikm */,
- authSecret /* salt */,
- keyInfo,
- WebPushConstants.IKM_SIZE);
- }
-
- public static byte[] computeCek(final byte[] ikm, final byte[] salt)
- throws GeneralSecurityException {
- return Hkdf.computeHkdf(
- WebPushConstants.HMAC_SHA256,
- ikm,
- salt,
- WebPushConstants.CEK_INFO,
- WebPushConstants.CEK_KEY_SIZE);
- }
-
- public static byte[] computeNonce(final byte[] ikm, final byte[] salt)
- throws GeneralSecurityException {
- return Hkdf.computeHkdf(
- WebPushConstants.HMAC_SHA256,
- ikm,
- salt,
- WebPushConstants.NONCE_INFO,
- WebPushConstants.NONCE_SIZE);
- }
-
- private WebPushUtil() {}
-}
diff --git a/apps/webpush/src/test/BUILD.bazel b/apps/webpush/src/test/BUILD.bazel
deleted file mode 100644
index 309b3ad..0000000
--- a/apps/webpush/src/test/BUILD.bazel
+++ /dev/null
@@ -1,38 +0,0 @@
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Tests
-
-java_library(
- name = "generator_test",
- testonly = 1,
- srcs = glob([
- "**/*.java",
- ]),
- deps = [
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_constants",
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_decrypt",
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_encrypt",
- "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_util",
- "@maven//:junit_junit",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
- "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:hex",
- "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
- "@tink_java//src/main/java/com/google/crypto/tink/testing:test_util",
- ],
-)
-
-gen_java_test_rules(
- test_files = glob([
- "**/*Test.java",
- ]),
- deps = [
- ":generator_test",
- ],
-)
diff --git a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java b/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java
deleted file mode 100644
index f8a6987..0000000
--- a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Random;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.Arrays;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code WebPushHybridDecrypt}. */
-@RunWith(JUnit4.class)
-public class WebPushHybridDecryptTest {
- // Copied from https://tools.ietf.org/html/rfc8291#section-5.
- private static final String PLAINTEXT = "V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24";
- private static final String RECEIVER_PRIVATE_KEY = "q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94";
- private static final String RECEIVER_PUBLIC_KEY =
- "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4";
- private static final String AUTH_SECRET = "BTBZMqHH6r4Tts7J_aSIgg";
- private static final String CIPHERTEXT =
- "DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27ml"
- + "mlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPT"
- + "pK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN";
- private static final int RECORD_SIZE = 4096;
-
- @Test
- public void testWithRfc8291TestVector() throws Exception {
- byte[] plaintext = Base64.urlSafeDecode(PLAINTEXT);
- byte[] recipientPrivateKey = Base64.urlSafeDecode(RECEIVER_PRIVATE_KEY);
- byte[] recipientPublicKey = Base64.urlSafeDecode(RECEIVER_PUBLIC_KEY);
- byte[] authSecret = Base64.urlSafeDecode(AUTH_SECRET);
- byte[] ciphertext = Base64.urlSafeDecode(CIPHERTEXT);
-
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(RECORD_SIZE)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(recipientPublicKey)
- .withRecipientPrivateKey(recipientPrivateKey)
- .build();
- assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
- }
-
- @Test
- public void testEncryptDecryptWithInvalidRecordSizes() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- // Test with out of range record sizes.
- {
- try {
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // expected.
- }
-
- try {
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
-
- } catch (IllegalArgumentException ex) {
- // expected.
- }
- }
-
- // Test with random mismatched record size.
- {
- for (int i = 0; i < 50; i++) {
- int recordSize =
- WebPushConstants.CIPHERTEXT_OVERHEAD
- + Random.randInt(
- WebPushConstants.MAX_CIPHERTEXT_SIZE
- - WebPushConstants.CIPHERTEXT_OVERHEAD
- - 1);
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withRecordSize(recordSize)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(recordSize + 1)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-
- try {
- hybridDecrypt.decrypt(ciphertext, null /* contextInfo */);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException ex) {
- // expected.
- }
- }
- }
- }
-
- @Test
- public void testNonNullContextInfo() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- byte[] plaintext = Random.randBytes(20);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
- try {
- byte[] contextInfo = new byte[0];
- hybridDecrypt.decrypt(ciphertext, contextInfo);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException ex) {
- // expected;
- }
- }
-
- @Test
- public void testModifyCiphertext() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- byte[] plaintext = Random.randBytes(20);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-
- // Flipping bits.
- for (int b = 0; b < ciphertext.length; b++) {
- for (int bit = 0; bit < 8; bit++) {
- byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length);
- modified[b] ^= (byte) (1 << bit);
- try {
- byte[] unused = hybridDecrypt.decrypt(modified, null /* contextInfo */);
- fail("Decrypting modified ciphertext should fail");
- } catch (GeneralSecurityException ex) {
- // This is expected.
- }
- }
- }
-
- // Truncate the message.
- for (int length = 0; length < ciphertext.length; length++) {
- byte[] modified = Arrays.copyOf(ciphertext, length);
- try {
- byte[] unused = hybridDecrypt.decrypt(modified, null /* contextInfo */);
- fail("Decrypting modified ciphertext should fail");
- } catch (GeneralSecurityException ex) {
- // This is expected.
- }
- }
- }
-
- @Test
- public void testEncryptDecrypt_withPadding_shouldWork() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- int paddingSize = 20;
- int plaintextSize = 20;
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withAuthSecret(authSecret)
- .withPaddingSize(paddingSize)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- byte[] plaintext = Random.randBytes(plaintextSize);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-
- assertEquals(
- ciphertext.length, plaintext.length + paddingSize + WebPushConstants.CIPHERTEXT_OVERHEAD);
- assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
- }
-}
diff --git a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java b/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java
deleted file mode 100644
index 7b84b84..0000000
--- a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Hex;
-import com.google.crypto.tink.subtle.Random;
-import com.google.crypto.tink.testing.TestUtil;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.Set;
-import java.util.TreeSet;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code WebPushHybridEncrypt}. */
-@RunWith(JUnit4.class)
-public class WebPushHybridEncryptTest {
- @Test
- public void testEncryptDecrypt() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] uaPublicKeyBytes =
- EllipticCurves.pointEncode(
- WebPushConstants.NIST_P256_CURVE_TYPE,
- WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
- uaPublicKey.getW());
- byte[] authSecret = Random.randBytes(16);
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKeyBytes)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKeyBytes)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
-
- Set<String> salts = new TreeSet<>();
- Set<String> ephemeralPublicKeys = new TreeSet<>();
- Set<String> payloads = new TreeSet<>();
- int numTests = 100;
- if (TestUtil.isTsan()) {
- numTests = 5;
- }
- for (int j = 0; j < numTests; j++) {
- byte[] plaintext = Random.randBytes(j);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
- assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
- assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-
- // Checks that the encryption is randomized.
- ByteBuffer record = ByteBuffer.wrap(ciphertext);
- byte[] salt = new byte[WebPushConstants.SALT_SIZE];
- record.get(salt);
- salts.add(Hex.encode(salt));
-
- int unused1 = record.getInt();
- int unused2 = (int) record.get();
-
- byte[] ephemeralPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE];
- record.get(ephemeralPublicKey);
- ephemeralPublicKeys.add(Hex.encode(ephemeralPublicKey));
-
- byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE];
- record.get(payload);
- payloads.add(Hex.encode(payload));
- }
- assertEquals(numTests, salts.size());
- assertEquals(numTests, ephemeralPublicKeys.size());
- assertEquals(numTests, payloads.size());
- }
-
- @Test
- public void testEncryptDecryptWithVaryingRecordSizes() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- int numTests = 100;
- if (TestUtil.isTsan()) {
- numTests = 5;
- }
- // Test with random, valid record sizes.
- for (int i = 0; i < numTests; i++) {
- int recordSize =
- WebPushConstants.CIPHERTEXT_OVERHEAD
- + Random.randInt(
- WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD);
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withRecordSize(recordSize)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(recordSize)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
-
- byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
- assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
- assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
- }
- }
-
- @Test
- public void testEncryptDecrypt_largestPossibleRecordSize() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
- // Test with largest possible record size.
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- byte[] plaintext =
- Random.randBytes(
- WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD);
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
- assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
- assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
- }
-
- @Test
- public void testEncryptDecrypt_smallestPossibleRecordSize() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
- // Test with smallest possible record size.
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- HybridDecrypt hybridDecrypt =
- new WebPushHybridDecrypt.Builder()
- .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .withRecipientPrivateKey(uaPrivateKey)
- .build();
- byte[] plaintext = new byte[0];
- byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
- assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
- assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
- }
-
- @Test
- public void testEncryptDecrypt_outOfRangeRecordSize_throws() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- try {
- new WebPushHybridEncrypt.Builder()
- .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // expected.
- }
-
- try {
- new WebPushHybridEncrypt.Builder()
- .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1)
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // expected.
- }
- }
-
- @Test
- public void testNonNullContextInfo() throws Exception {
- KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
- ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
- byte[] authSecret = Random.randBytes(16);
-
- HybridEncrypt hybridEncrypt =
- new WebPushHybridEncrypt.Builder()
- .withAuthSecret(authSecret)
- .withRecipientPublicKey(uaPublicKey)
- .build();
- byte[] plaintext = Random.randBytes(20);
- byte[] contextInfo = new byte[0];
-
- try {
- byte[] unusedCiphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
- fail("Expected GeneralSecurityException");
- } catch (GeneralSecurityException ex) {
- // expected;
- }
- }
-}
-
diff --git a/cc/.bazelrc b/cc/.bazelrc
index 94aad05..e2435c9 100644
--- a/cc/.bazelrc
+++ b/cc/.bazelrc
@@ -1,3 +1,7 @@
# Fix for grpc build error on macOS.
# See: https://github.com/bazelbuild/bazel/issues/4341
-build --copt -DGRPC_BAZEL_BUILD
+build --copt=-DGRPC_BAZEL_BUILD
+# Minumum C++ version. Override it building this project with
+# `bazel build --cxxopt='-std=c++<XY>' --host_cxxopt='c++<XY>' ...`
+# (Both -std and --host_cxxopt must be set to force the desired version.)
+build --cxxopt='-std=c++14' --host_cxxopt='-std=c++14'
diff --git a/cc/.bazelversion b/cc/.bazelversion
index ac14c3d..09b254e 100644
--- a/cc/.bazelversion
+++ b/cc/.bazelversion
@@ -1 +1 @@
-5.1.1
+6.0.0
diff --git a/cc/BUILD.bazel b/cc/BUILD.bazel
index 5064fdb..c88dd8e 100644
--- a/cc/BUILD.bazel
+++ b/cc/BUILD.bazel
@@ -23,8 +23,6 @@
"aead_key_templates.h",
"binary_keyset_reader.h",
"binary_keyset_writer.h",
- "catalogue.h",
- "config.h",
"deterministic_aead.h",
"deterministic_aead_config.h",
"deterministic_aead_factory.h",
@@ -77,6 +75,7 @@
":input_stream",
":json_keyset_reader",
":json_keyset_writer",
+ ":key",
":key_manager",
":keyset_handle",
":keyset_manager",
@@ -396,41 +395,6 @@
)
cc_library(
- name = "catalogue",
- hdrs = ["catalogue.h"],
- include_prefix = "tink",
- deps = [
- ":key_manager",
- "//util:statusor",
- "@com_google_absl//absl/base:core_headers",
- ],
-)
-
-cc_library(
- name = "config",
- srcs = ["core/config.cc"],
- hdrs = ["config.h"],
- include_prefix = "tink",
- deps = [
- ":catalogue",
- ":key_manager",
- ":registry",
- "//aead:aead_config",
- "//daead:deterministic_aead_config",
- "//hybrid:hybrid_config",
- "//mac:mac_config",
- "//proto:config_cc_proto",
- "//signature:signature_config",
- "//streamingaead:streaming_aead_config",
- "//util:errors",
- "//util:status",
- "//util:statusor",
- "@com_google_absl//absl/status",
- "@com_google_absl//absl/strings",
- ],
-)
-
-cc_library(
name = "crypto_format",
srcs = ["core/crypto_format.cc"],
hdrs = ["crypto_format.h"],
@@ -453,6 +417,7 @@
"//proto:tink_cc_proto",
"//util:errors",
"//util:statusor",
+ "@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
@@ -507,20 +472,54 @@
visibility = ["//visibility:public"],
deps = [
":aead",
+ ":configuration",
+ ":insecure_secret_key_access",
+ ":key",
+ ":key_gen_configuration",
":key_manager",
+ ":key_status",
":keyset_reader",
":keyset_writer",
":primitive_set",
":registry",
+ "//internal:configuration_impl",
+ "//internal:key_gen_configuration_impl",
"//internal:key_info",
+ "//internal:key_status_util",
+ "//internal:mutable_serialization_registry",
+ "//internal:proto_key_serialization",
+ "//internal:util",
"//proto:tink_cc_proto",
"//util:errors",
"//util:keyset_util",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
+ "@com_google_absl//absl/log:check",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:optional",
+ ],
+)
+
+cc_library(
+ name = "keyset_handle_builder",
+ srcs = ["core/keyset_handle_builder.cc"],
+ hdrs = ["keyset_handle_builder.h"],
+ include_prefix = "tink",
+ visibility = ["//visibility:public"],
+ deps = [
+ ":key",
+ ":key_status",
+ ":keyset_handle",
+ ":parameters",
+ "//internal:keyset_handle_builder_entry",
+ "//proto:tink_cc_proto",
+ "//subtle:random",
+ "@com_google_absl//absl/log:check",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:optional",
],
)
@@ -565,9 +564,9 @@
include_prefix = "tink",
visibility = ["//visibility:public"],
deps = [
+ ":key_gen_configuration",
":keyset_handle",
- ":keyset_reader",
- ":registry",
+ "//internal:key_gen_configuration_impl",
"//proto:tink_cc_proto",
"//util:enums",
"//util:errors",
@@ -703,6 +702,19 @@
)
cc_library(
+ name = "partial_key_access_token",
+ hdrs = ["partial_key_access_token.h"],
+ include_prefix = "tink",
+)
+
+cc_library(
+ name = "partial_key_access",
+ hdrs = ["partial_key_access.h"],
+ include_prefix = "tink",
+ deps = [":partial_key_access_token"],
+)
+
+cc_library(
name = "secret_key_access_token",
hdrs = ["secret_key_access_token.h"],
include_prefix = "tink",
@@ -712,9 +724,31 @@
name = "insecure_secret_key_access",
hdrs = ["insecure_secret_key_access.h"],
include_prefix = "tink",
+ visibility = ["//visibility:public"],
deps = [":secret_key_access_token"],
)
+cc_library(
+ name = "restricted_data",
+ srcs = ["core/restricted_data.cc"],
+ hdrs = ["restricted_data.h"],
+ include_prefix = "tink",
+ deps = [
+ ":secret_key_access_token",
+ "//subtle:random",
+ "//util:secret_data",
+ "@boringssl//:crypto",
+ "@com_google_absl//absl/log:check",
+ ],
+)
+
+cc_library(
+ name = "key_status",
+ hdrs = ["key_status.h"],
+ include_prefix = "tink",
+ visibility = ["//visibility:public"],
+)
+
# tests
cc_test(
@@ -723,6 +757,7 @@
srcs = ["core/version_test.cc"],
deps = [
":version",
+ "//internal:util",
"@com_google_googletest//:gtest_main",
],
)
@@ -788,18 +823,6 @@
)
cc_test(
- name = "config_test",
- size = "small",
- srcs = ["core/config_test.cc"],
- deps = [
- ":config",
- ":mac",
- "//proto:config_cc_proto",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
name = "crypto_format_test",
size = "small",
srcs = ["core/crypto_format_test.cc"],
@@ -820,6 +843,8 @@
":core/key_manager_impl",
":json_keyset_reader",
":json_keyset_writer",
+ ":key_gen_configuration",
+ ":key_status",
":keyset_handle",
":primitive_set",
":primitive_wrapper",
@@ -827,11 +852,16 @@
"//aead:aead_key_templates",
"//aead:aead_wrapper",
"//aead:aes_gcm_key_manager",
+ "//config:fips_140_2",
+ "//config:key_gen_fips_140_2",
"//config:tink_config",
+ "//config/internal:global_registry",
+ "//internal:fips_utils",
+ "//internal:key_gen_configuration_impl",
+ "//proto:aes_gcm_siv_cc_proto",
"//proto:tink_cc_proto",
"//signature:ecdsa_sign_key_manager",
"//signature:signature_key_templates",
- "//util:protobuf_helper",
"//util:status",
"//util:test_keyset_handle",
"//util:test_matchers",
@@ -843,6 +873,34 @@
)
cc_test(
+ name = "keyset_handle_builder_test",
+ srcs = ["core/keyset_handle_builder_test.cc"],
+ deps = [
+ ":insecure_secret_key_access",
+ ":key_status",
+ ":keyset_handle_builder",
+ ":partial_key_access",
+ "//config:tink_config",
+ "//internal:legacy_proto_key",
+ "//internal:legacy_proto_parameters",
+ "//internal:proto_key_serialization",
+ "//internal:proto_parameters_serialization",
+ "//mac:aes_cmac_key",
+ "//mac:aes_cmac_parameters",
+ "//mac:mac_key_templates",
+ "//proto:aes_cmac_cc_proto",
+ "//proto:tink_cc_proto",
+ "//subtle:random",
+ "//util:status",
+ "//util:test_matchers",
+ "//util:test_util",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/strings",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
name = "key_manager_test",
size = "small",
srcs = ["core/key_manager_test.cc"],
@@ -861,7 +919,6 @@
size = "small",
srcs = ["core/keyset_manager_test.cc"],
deps = [
- ":config",
":keyset_handle",
":keyset_manager",
"//aead:aead_config",
@@ -893,9 +950,11 @@
size = "small",
srcs = ["core/primitive_set_test.cc"],
deps = [
+ ":cleartext_keyset_handle",
":crypto_format",
":mac",
":primitive_set",
+ "//keyderivation:keyset_deriver",
"//proto:tink_cc_proto",
"//util:test_matchers",
"//util:test_util",
@@ -999,3 +1058,82 @@
"@com_google_googletest//:gtest_main",
],
)
+
+cc_test(
+ name = "core/partial_key_access_token_test",
+ srcs = ["core/partial_key_access_token_test.cc"],
+ deps = [
+ ":partial_key_access",
+ ":partial_key_access_token",
+ "@com_google_absl//absl/base:core_headers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "core/restricted_data_test",
+ srcs = ["core/restricted_data_test.cc"],
+ deps = [
+ ":insecure_secret_key_access",
+ ":restricted_data",
+ "//subtle:random",
+ "//util:secret_data",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "proto_keyset_format",
+ srcs = ["proto_keyset_format.cc"],
+ hdrs = ["proto_keyset_format.h"],
+ include_prefix = "tink",
+ visibility = ["//visibility:public"],
+ deps = [
+ ":binary_keyset_reader",
+ ":binary_keyset_writer",
+ ":cleartext_keyset_handle",
+ ":keyset_handle",
+ ":secret_key_access_token",
+ "//util:secret_data",
+ "@com_google_absl//absl/strings",
+ ],
+)
+
+cc_test(
+ name = "proto_keyset_format_test",
+ srcs = ["proto_keyset_format_test.cc"],
+ deps = [
+ ":insecure_secret_key_access",
+ ":keyset_handle_builder",
+ ":mac",
+ ":proto_keyset_format",
+ "//config:tink_config",
+ "//internal:legacy_proto_parameters",
+ "//internal:proto_parameters_serialization",
+ "//mac:mac_key_templates",
+ "//signature:signature_key_templates",
+ "//util:secret_data",
+ "//util:test_matchers",
+ "@com_google_absl//absl/strings",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "configuration",
+ hdrs = ["configuration.h"],
+ include_prefix = "tink",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//internal:key_type_info_store",
+ "//internal:keyset_wrapper_store",
+ ],
+)
+
+cc_library(
+ name = "key_gen_configuration",
+ hdrs = ["key_gen_configuration.h"],
+ include_prefix = "tink",
+ visibility = ["//visibility:public"],
+ deps = ["//internal:key_type_info_store"],
+)
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 2ef21b2..e13bee0 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -181,19 +181,6 @@
public_configs = [ "//third_party/tink:tink_config" ]
}
-# CC Library : catalogue
-source_set("catalogue") {
- configs += [ "//build/config:no_rtti" ]
- configs -= [ "//build/config:no_rtti" ]
- sources = [ "catalogue.h" ]
- public_deps = [
- ":key_manager",
- "//third_party/abseil-cpp/absl/base:core_headers",
- "//third_party/tink/cc/util:statusor",
- ]
- public_configs = [ "//third_party/tink:tink_config" ]
-}
-
# CC Library : crypto_format
source_set("crypto_format") {
configs += [ "//build/config:no_rtti" ]
@@ -221,6 +208,7 @@
]
public_deps = [
":crypto_format",
+ "//third_party/abseil-cpp/absl/base:core_headers",
"//third_party/abseil-cpp/absl/container:flat_hash_map",
"//third_party/abseil-cpp/absl/memory:memory",
"//third_party/abseil-cpp/absl/status:status",
@@ -268,17 +256,30 @@
]
public_deps = [
":aead",
+ ":configuration",
+ ":insecure_secret_key_access",
+ ":key",
+ ":key_gen_configuration",
":key_manager",
+ ":key_status",
":keyset_reader",
":keyset_writer",
":primitive_set",
":registry",
"//third_party/abseil-cpp/absl/base:core_headers",
"//third_party/abseil-cpp/absl/container:flat_hash_map",
+ "//third_party/abseil-cpp/absl/log:check",
"//third_party/abseil-cpp/absl/memory:memory",
"//third_party/abseil-cpp/absl/status:status",
"//third_party/abseil-cpp/absl/strings:strings",
+ "//third_party/abseil-cpp/absl/types:optional",
+ "//third_party/tink/cc/internal:configuration_impl",
+ "//third_party/tink/cc/internal:key_gen_configuration_impl",
"//third_party/tink/cc/internal:key_info",
+ "//third_party/tink/cc/internal:key_status_util",
+ "//third_party/tink/cc/internal:mutable_serialization_registry",
+ "//third_party/tink/cc/internal:proto_key_serialization",
+ "//third_party/tink/cc/internal:util",
"//third_party/tink/cc/proto:tink_proto",
"//third_party/tink/cc/util:errors",
"//third_party/tink/cc/util:keyset_util",
@@ -433,3 +434,104 @@
]
public_configs = [ "//third_party/tink:tink_config" ]
}
+
+# CC Library : parameters
+source_set("parameters") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "parameters.h" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key
+source_set("key") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "key.h" ]
+ public_deps = [
+ ":parameters",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : partial_key_access_token
+source_set("partial_key_access_token") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "partial_key_access_token.h" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : partial_key_access
+source_set("partial_key_access") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "partial_key_access.h" ]
+ public_deps = [ ":partial_key_access_token" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : secret_key_access_token
+source_set("secret_key_access_token") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "secret_key_access_token.h" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : insecure_secret_key_access
+source_set("insecure_secret_key_access") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "insecure_secret_key_access.h" ]
+ public_deps = [ ":secret_key_access_token" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : restricted_data
+source_set("restricted_data") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [
+ "core/restricted_data.cc",
+ "restricted_data.h",
+ ]
+ public_deps = [
+ ":secret_key_access_token",
+ "//third_party/abseil-cpp/absl/log:check",
+ "//third_party/boringssl:crypto",
+ "//third_party/tink/cc/subtle:random",
+ "//third_party/tink/cc/util:secret_data",
+ ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_status
+source_set("key_status") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "key_status.h" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : configuration
+source_set("configuration") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "configuration.h" ]
+ public_deps = [
+ "//third_party/tink/cc/internal:key_type_info_store",
+ "//third_party/tink/cc/internal:keyset_wrapper_store",
+ ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_gen_configuration
+source_set("key_gen_configuration") {
+ configs += [ "//build/config:no_rtti" ]
+ configs -= [ "//build/config:no_rtti" ]
+ sources = [ "key_gen_configuration.h" ]
+ public_deps = [ "//third_party/tink/cc/internal:key_type_info_store" ]
+ public_configs = [ "//third_party/tink:tink_config" ]
+}
diff --git a/cc/CMakeLists.txt b/cc/CMakeLists.txt
index a7defba..1cd07e4 100644
--- a/cc/CMakeLists.txt
+++ b/cc/CMakeLists.txt
@@ -6,6 +6,7 @@
add_subdirectory(mac)
add_subdirectory(monitoring)
add_subdirectory(jwt)
+add_subdirectory(keyderivation)
add_subdirectory(prf)
add_subdirectory(signature)
add_subdirectory(streamingaead)
@@ -16,12 +17,6 @@
# Configuration settings for the build.
-if (TINK_USE_ABSL_STATUS)
- target_compile_definitions(tink_util_status PUBLIC TINK_USE_ABSL_STATUS)
-endif()
-if (TINK_USE_ABSL_STATUSOR)
- target_compile_definitions(tink_util_statusor INTERFACE TINK_USE_ABSL_STATUSOR)
-endif()
if(USE_ONLY_FIPS)
target_compile_definitions(tink_internal_fips_utils PUBLIC TINK_USE_ONLY_FIPS)
endif()
@@ -39,9 +34,7 @@
aead_key_templates.h
binary_keyset_reader.h
binary_keyset_writer.h
- catalogue.h
cleartext_keyset_handle.h
- config.h
deterministic_aead.h
deterministic_aead_config.h
deterministic_aead_factory.h
@@ -92,6 +85,7 @@
tink::core::input_stream
tink::core::json_keyset_reader
tink::core::json_keyset_writer
+ tink::core::key
tink::core::key_manager
tink::core::keyset_handle
tink::core::keyset_manager
@@ -373,39 +367,6 @@
)
tink_cc_library(
- NAME catalogue
- SRCS
- catalogue.h
- DEPS
- tink::core::key_manager
- absl::core_headers
- tink::util::statusor
-)
-
-tink_cc_library(
- NAME config
- SRCS
- core/config.cc
- config.h
- DEPS
- tink::core::catalogue
- tink::core::key_manager
- tink::core::registry
- absl::status
- absl::strings
- tink::aead::aead_config
- tink::daead::deterministic_aead_config
- tink::hybrid::hybrid_config
- tink::mac::mac_config
- tink::signature::signature_config
- tink::streamingaead::streaming_aead_config
- tink::util::errors
- tink::util::status
- tink::util::statusor
- tink::proto::config_cc_proto
-)
-
-tink_cc_library(
NAME crypto_format
SRCS
core/crypto_format.cc
@@ -424,6 +385,7 @@
primitive_set.h
DEPS
tink::core::crypto_format
+ absl::core_headers
absl::flat_hash_map
absl::memory
absl::status
@@ -472,23 +434,55 @@
keyset_handle.h
DEPS
tink::core::aead
+ tink::core::configuration
+ tink::core::insecure_secret_key_access
+ tink::core::key
+ tink::core::key_gen_configuration
tink::core::key_manager
+ tink::core::key_status
tink::core::keyset_reader
tink::core::keyset_writer
tink::core::primitive_set
tink::core::registry
absl::core_headers
absl::flat_hash_map
+ absl::check
absl::memory
absl::status
absl::strings
+ absl::optional
+ tink::internal::configuration_impl
+ tink::internal::key_gen_configuration_impl
tink::internal::key_info
+ tink::internal::key_status_util
+ tink::internal::mutable_serialization_registry
+ tink::internal::proto_key_serialization
+ tink::internal::util
tink::util::errors
tink::util::keyset_util
tink::proto::tink_cc_proto
)
tink_cc_library(
+ NAME keyset_handle_builder
+ SRCS
+ core/keyset_handle_builder.cc
+ keyset_handle_builder.h
+ DEPS
+ tink::core::key
+ tink::core::key_status
+ tink::core::keyset_handle
+ tink::core::parameters
+ absl::check
+ absl::status
+ absl::strings
+ absl::optional
+ tink::internal::keyset_handle_builder_entry
+ tink::subtle::random
+ tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
NAME cleartext_keyset_handle
SRCS
core/cleartext_keyset_handle.cc
@@ -525,13 +519,13 @@
core/keyset_manager.cc
keyset_manager.h
DEPS
+ tink::core::key_gen_configuration
tink::core::keyset_handle
- tink::core::keyset_reader
- tink::core::registry
absl::core_headers
absl::memory
absl::status
absl::synchronization
+ tink::internal::key_gen_configuration_impl
tink::util::enums
tink::util::errors
tink::util::status
@@ -658,6 +652,26 @@
)
tink_cc_library(
+ NAME partial_key_access_token
+ SRCS
+ partial_key_access_token.h
+)
+
+tink_cc_library(
+ NAME partial_key_access
+ SRCS
+ partial_key_access.h
+ DEPS
+ tink::core::partial_key_access_token
+)
+
+tink_cc_library(
+ NAME secret_key_access_token
+ SRCS
+ secret_key_access_token.h
+)
+
+tink_cc_library(
NAME insecure_secret_key_access
SRCS
insecure_secret_key_access.h
@@ -666,9 +680,22 @@
)
tink_cc_library(
- NAME secret_key_access_token
+ NAME restricted_data
SRCS
- secret_key_access_token.h
+ core/restricted_data.cc
+ restricted_data.h
+ DEPS
+ tink::core::secret_key_access_token
+ absl::check
+ crypto
+ tink::subtle::random
+ tink::util::secret_data
+)
+
+tink_cc_library(
+ NAME key_status
+ SRCS
+ key_status.h
)
# tests
@@ -680,6 +707,7 @@
DEPS
tink::core::version
gmock
+ tink::internal::util
)
tink_cc_test(
@@ -739,17 +767,6 @@
)
tink_cc_test(
- NAME config_test
- SRCS
- core/config_test.cc
- DEPS
- tink::core::config
- tink::core::mac
- gmock
- tink::proto::config_cc_proto
-)
-
-tink_cc_test(
NAME crypto_format_test
SRCS
core/crypto_format_test.cc
@@ -770,6 +787,8 @@
tink::core::key_manager_impl
tink::core::json_keyset_reader
tink::core::json_keyset_writer
+ tink::core::key_gen_configuration
+ tink::core::key_status
tink::core::keyset_handle
tink::core::primitive_set
tink::core::primitive_wrapper
@@ -779,14 +798,47 @@
tink::aead::aead_key_templates
tink::aead::aead_wrapper
tink::aead::aes_gcm_key_manager
+ tink::config::fips_140_2
+ tink::config::key_gen_fips_140_2
tink::config::tink_config
+ tink::config::internal::global_registry
+ tink::internal::fips_utils
+ tink::internal::key_gen_configuration_impl
tink::signature::ecdsa_sign_key_manager
tink::signature::signature_key_templates
- tink::util::protobuf_helper
tink::util::status
tink::util::test_keyset_handle
tink::util::test_matchers
tink::util::test_util
+ tink::proto::aes_gcm_siv_cc_proto
+ tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+ NAME keyset_handle_builder_test
+ SRCS
+ core/keyset_handle_builder_test.cc
+ DEPS
+ tink::core::insecure_secret_key_access
+ tink::core::key_status
+ tink::core::keyset_handle_builder
+ tink::core::partial_key_access
+ gmock
+ absl::status
+ absl::strings
+ tink::config::tink_config
+ tink::internal::legacy_proto_key
+ tink::internal::legacy_proto_parameters
+ tink::internal::proto_key_serialization
+ tink::internal::proto_parameters_serialization
+ tink::mac::aes_cmac_key
+ tink::mac::aes_cmac_parameters
+ tink::mac::mac_key_templates
+ tink::subtle::random
+ tink::util::status
+ tink::util::test_matchers
+ tink::util::test_util
+ tink::proto::aes_cmac_cc_proto
tink::proto::tink_cc_proto
)
@@ -808,7 +860,6 @@
SRCS
core/keyset_manager_test.cc
DEPS
- tink::core::config
tink::core::keyset_handle
tink::core::keyset_manager
gmock
@@ -838,10 +889,12 @@
SRCS
core/primitive_set_test.cc
DEPS
+ tink::core::cleartext_keyset_handle
tink::core::crypto_format
tink::core::mac
tink::core::primitive_set
gmock
+ tink::keyderivation::keyset_deriver
tink::util::test_matchers
tink::util::test_util
tink::proto::tink_cc_proto
@@ -942,3 +995,78 @@
tink::core::secret_key_access_testonly
gmock
)
+
+tink_cc_test(
+ NAME partial_key_access_token_test
+ SRCS
+ core/partial_key_access_token_test.cc
+ DEPS
+ tink::core::partial_key_access
+ tink::core::partial_key_access_token
+ gmock
+ absl::core_headers
+)
+
+tink_cc_test(
+ NAME restricted_data_test
+ SRCS
+ core/restricted_data_test.cc
+ DEPS
+ tink::core::insecure_secret_key_access
+ tink::core::restricted_data
+ gmock
+ tink::subtle::random
+ tink::util::secret_data
+)
+
+tink_cc_library(
+ NAME proto_keyset_format
+ SRCS
+ proto_keyset_format.cc
+ proto_keyset_format.h
+ DEPS
+ tink::core::binary_keyset_reader
+ tink::core::binary_keyset_writer
+ tink::core::cleartext_keyset_handle
+ tink::core::keyset_handle
+ tink::core::secret_key_access_token
+ absl::strings
+ tink::util::secret_data
+)
+
+tink_cc_test(
+ NAME proto_keyset_format_test
+ SRCS
+ proto_keyset_format_test.cc
+ DEPS
+ tink::core::insecure_secret_key_access
+ tink::core::keyset_handle_builder
+ tink::core::mac
+ tink::core::proto_keyset_format
+ gmock
+ absl::strings
+ tink::config::tink_config
+ tink::internal::legacy_proto_parameters
+ tink::internal::proto_parameters_serialization
+ tink::mac::mac_key_templates
+ tink::signature::signature_key_templates
+ tink::util::secret_data
+ tink::util::test_matchers
+)
+
+tink_cc_library(
+ NAME configuration
+ SRCS
+ configuration.h
+ DEPS
+ tink::internal::key_type_info_store
+ tink::internal::keyset_wrapper_store
+)
+
+tink_cc_library(
+ NAME key_gen_configuration
+ SRCS
+ key_gen_configuration.h
+ DEPS
+ tink::internal::key_type_info_store
+)
diff --git a/cc/MODULE.bazel b/cc/MODULE.bazel
new file mode 100644
index 0000000..ab23a48
--- /dev/null
+++ b/cc/MODULE.bazel
@@ -0,0 +1,32 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tink C++ Bazel Module definition."""
+module(
+ name = "tink_cc",
+ version = "2.0.0",
+)
+
+bazel_dep(name = "rules_cc", version = "0.0.5")
+bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
+bazel_dep(name = "platforms", version = "0.0.6")
+bazel_dep(name = "bazel_skylib", version = "1.3.0")
+bazel_dep(name = "googletest", version = "1.12.1", repo_name = "com_google_googletest")
+bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf")
+bazel_dep(name = "boringssl", version = "0.0.0-20230215-5c22014")
+bazel_dep(name = "rapidjson", version = "1.1.0")
+bazel_dep(name = "abseil-cpp", version = "20230125.1", repo_name="com_google_absl")
+
+wycheproof_extension = use_extension("//:extensions.bzl", "wycheproof_extension")
+use_repo(wycheproof_extension, "wycheproof")
diff --git a/cc/WORKSPACE b/cc/WORKSPACE
index ca79c62..af5976a 100644
--- a/cc/WORKSPACE
+++ b/cc/WORKSPACE
@@ -13,9 +13,3 @@
load("@tink_cc//:tink_cc_deps_init.bzl", "tink_cc_deps_init")
tink_cc_deps_init()
-
-load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
-
-# Creates a default toolchain config for RBE. Use this as is if you are
-# using the rbe_ubuntu16_04 container, otherwise refer to RBE docs.
-rbe_autoconfig(name = "rbe_default")
diff --git a/cc/WORKSPACE.bzlmod b/cc/WORKSPACE.bzlmod
new file mode 100644
index 0000000..057d6aa
--- /dev/null
+++ b/cc/WORKSPACE.bzlmod
@@ -0,0 +1 @@
+# This replaces the content of the WORKSPACE file when using --enable_bzlmod.
diff --git a/cc/aead.h b/cc/aead.h
index 803fc51..45017d6 100644
--- a/cc/aead.h
+++ b/cc/aead.h
@@ -51,7 +51,7 @@
absl::string_view ciphertext,
absl::string_view associated_data) const = 0;
- virtual ~Aead() {}
+ virtual ~Aead() = default;
};
} // namespace tink
diff --git a/cc/aead/BUILD.bazel b/cc/aead/BUILD.bazel
index 28b3e8b..10648c4 100644
--- a/cc/aead/BUILD.bazel
+++ b/cc/aead/BUILD.bazel
@@ -214,6 +214,7 @@
"//subtle:random",
"//util:constants",
"//util:enums",
+ "//util:input_stream_util",
"//util:secret_data",
"//util:status",
"//util:statusor",
@@ -281,6 +282,7 @@
deps = [
"//:aead",
"//:registry",
+ "//aead/internal:aead_util",
"//proto:tink_cc_proto",
"//util:status",
"//util:statusor",
@@ -302,6 +304,7 @@
"//:core/template_util",
"//:kms_client",
"//:kms_clients",
+ "//aead/internal:aead_util",
"//internal:fips_utils",
"//proto:kms_envelope_cc_proto",
"//proto:tink_cc_proto",
@@ -329,6 +332,95 @@
],
)
+cc_library(
+ name = "failing_aead",
+ testonly = 1,
+ srcs = ["failing_aead.cc"],
+ hdrs = ["failing_aead.h"],
+ include_prefix = "tink/aead",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//:aead",
+ "@com_google_absl//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "aead_parameters",
+ hdrs = ["aead_parameters.h"],
+ include_prefix = "tink/aead",
+ deps = ["//:parameters"],
+)
+
+cc_library(
+ name = "aead_key",
+ hdrs = ["aead_key.h"],
+ include_prefix = "tink/aead",
+ deps = [
+ ":aead_parameters",
+ "//:key",
+ "@com_google_absl//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "aes_gcm_parameters",
+ srcs = ["aes_gcm_parameters.cc"],
+ hdrs = ["aes_gcm_parameters.h"],
+ include_prefix = "tink/aead",
+ deps = [
+ ":aead_parameters",
+ "//util:status",
+ "//util:statusor",
+ "@com_google_absl//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "aes_gcm_key",
+ srcs = ["aes_gcm_key.cc"],
+ hdrs = ["aes_gcm_key.h"],
+ include_prefix = "tink/aead",
+ deps = [
+ ":aead_key",
+ ":aes_gcm_parameters",
+ "//:partial_key_access_token",
+ "//:restricted_data",
+ "//subtle:subtle_util",
+ "//util:status",
+ "//util:statusor",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:optional",
+ ],
+)
+
+cc_library(
+ name = "aes_gcm_proto_serialization",
+ srcs = ["aes_gcm_proto_serialization.cc"],
+ hdrs = ["aes_gcm_proto_serialization.h"],
+ include_prefix = "tink/aead",
+ deps = [
+ ":aes_gcm_key",
+ ":aes_gcm_parameters",
+ "//:partial_key_access",
+ "//:restricted_data",
+ "//:secret_key_access_token",
+ "//internal:key_parser",
+ "//internal:key_serializer",
+ "//internal:mutable_serialization_registry",
+ "//internal:parameters_parser",
+ "//internal:parameters_serializer",
+ "//internal:proto_key_serialization",
+ "//internal:proto_parameters_serialization",
+ "//proto:aes_gcm_cc_proto",
+ "//proto:tink_cc_proto",
+ "//util:status",
+ "//util:statusor",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/types:optional",
+ ],
+)
+
# tests
cc_test(
@@ -373,11 +465,11 @@
"//:primitive_set",
"//:registry",
"//config:tink_fips",
+ "//internal:fips_utils",
"//proto:tink_cc_proto",
"//util:status",
"//util:statusor",
"//util:test_matchers",
- "//util:test_util",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_googletest//:gtest_main",
@@ -516,7 +608,6 @@
deps = [
":aes_ctr_hmac_aead_key_manager",
"//:aead",
- "//:mac",
"//proto:aes_ctr_cc_proto",
"//proto:aes_ctr_hmac_aead_cc_proto",
"//proto:common_cc_proto",
@@ -526,6 +617,7 @@
"//subtle:aead_test_util",
"//subtle:aes_ctr_boringssl",
"//util:enums",
+ "//util:istream_input_stream",
"//util:secret_data",
"//util:status",
"//util:statusor",
@@ -589,7 +681,9 @@
":aead_key_templates",
":kms_envelope_aead",
"//:aead",
+ "//:keyset_handle",
"//:registry",
+ "//internal:ssl_util",
"//mac:mac_key_templates",
"//proto:aes_gcm_cc_proto",
"//util:status",
@@ -599,6 +693,7 @@
"@com_google_absl//absl/base:endian",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
+ "@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)
@@ -608,6 +703,7 @@
size = "small",
srcs = ["kms_envelope_aead_key_manager_test.cc"],
deps = [
+ ":aead_config",
":aead_key_templates",
":aes_eax_key_manager",
":kms_envelope_aead",
@@ -616,9 +712,11 @@
"//:kms_client",
"//:kms_clients",
"//:registry",
+ "//mac:mac_key_templates",
"//proto:kms_envelope_cc_proto",
"//proto:tink_cc_proto",
"//subtle:aead_test_util",
+ "//util:fake_kms_client",
"//util:status",
"//util:statusor",
"//util:test_matchers",
@@ -650,19 +748,6 @@
],
)
-cc_library(
- name = "failing_aead",
- testonly = 1,
- srcs = ["failing_aead.cc"],
- hdrs = ["failing_aead.h"],
- include_prefix = "tink/aead",
- visibility = ["//visibility:public"],
- deps = [
- "//:aead",
- "@com_google_absl//absl/strings",
- ],
-)
-
cc_test(
name = "failing_aead_test",
srcs = ["failing_aead_test.cc"],
@@ -673,3 +758,51 @@
"@com_google_googletest//:gtest_main",
],
)
+
+cc_test(
+ name = "aes_gcm_parameters_test",
+ srcs = ["aes_gcm_parameters_test.cc"],
+ deps = [
+ ":aes_gcm_parameters",
+ "//util:statusor",
+ "//util:test_matchers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "aes_gcm_key_test",
+ srcs = ["aes_gcm_key_test.cc"],
+ deps = [
+ ":aes_gcm_key",
+ ":aes_gcm_parameters",
+ "//:partial_key_access",
+ "//:restricted_data",
+ "//util:statusor",
+ "//util:test_matchers",
+ "@com_google_absl//absl/types:optional",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "aes_gcm_proto_serialization_test",
+ size = "small",
+ srcs = ["aes_gcm_proto_serialization_test.cc"],
+ deps = [
+ ":aes_gcm_key",
+ ":aes_gcm_parameters",
+ ":aes_gcm_proto_serialization",
+ "//:insecure_secret_key_access",
+ "//:partial_key_access",
+ "//:restricted_data",
+ "//internal:mutable_serialization_registry",
+ "//internal:proto_key_serialization",
+ "//internal:proto_parameters_serialization",
+ "//proto:aes_gcm_cc_proto",
+ "//proto:tink_cc_proto",
+ "//subtle:random",
+ "//util:test_matchers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/cc/aead/BUILD.gn b/cc/aead/BUILD.gn
index f4dc781..6eae32e 100644
--- a/cc/aead/BUILD.gn
+++ b/cc/aead/BUILD.gn
@@ -204,6 +204,7 @@
"//third_party/tink/cc/subtle:random",
"//third_party/tink/cc/util:constants",
"//third_party/tink/cc/util:enums",
+ "//third_party/tink/cc/util:input_stream_util",
"//third_party/tink/cc/util:secret_data",
"//third_party/tink/cc/util:status",
"//third_party/tink/cc/util:statusor",
@@ -277,6 +278,7 @@
"//third_party/abseil-cpp/absl/strings:strings",
"//third_party/tink/cc:aead",
"//third_party/tink/cc:registry",
+ "//third_party/tink/cc/aead/internal:aead_util",
"//third_party/tink/cc/proto:tink_proto",
"//third_party/tink/cc/util:status",
"//third_party/tink/cc/util:statusor",
@@ -302,6 +304,7 @@
"//third_party/tink/cc:core/template_util",
"//third_party/tink/cc:kms_client",
"//third_party/tink/cc:kms_clients",
+ "//third_party/tink/cc/aead/internal:aead_util",
"//third_party/tink/cc/internal:fips_utils",
"//third_party/tink/cc/proto:kms_envelope_proto",
"//third_party/tink/cc/proto:tink_proto",
diff --git a/cc/aead/CMakeLists.txt b/cc/aead/CMakeLists.txt
index c4adc4e..41264b9 100644
--- a/cc/aead/CMakeLists.txt
+++ b/cc/aead/CMakeLists.txt
@@ -199,6 +199,7 @@
tink::subtle::random
tink::util::constants
tink::util::enums
+ tink::util::input_stream_util
tink::util::secret_data
tink::util::status
tink::util::statusor
@@ -266,6 +267,7 @@
absl::strings
tink::core::aead
tink::core::registry
+ tink::aead::internal::aead_util
tink::util::status
tink::util::statusor
tink::proto::tink_cc_proto
@@ -286,6 +288,7 @@
tink::core::template_util
tink::core::kms_client
tink::core::kms_clients
+ tink::aead::internal::aead_util
tink::internal::fips_utils
tink::util::constants
tink::util::status
@@ -304,6 +307,91 @@
absl::strings
tink::core::aead
tink::util::statusor
+ TESTONLY
+)
+
+tink_cc_library(
+ NAME failing_aead
+ SRCS
+ failing_aead.cc
+ failing_aead.h
+ DEPS
+ absl::strings
+ tink::core::aead
+ TESTONLY
+)
+
+tink_cc_library(
+ NAME aead_parameters
+ SRCS
+ aead_parameters.h
+ DEPS
+ tink::core::parameters
+)
+
+tink_cc_library(
+ NAME aead_key
+ SRCS
+ aead_key.h
+ DEPS
+ tink::aead::aead_parameters
+ absl::strings
+ tink::core::key
+)
+
+tink_cc_library(
+ NAME aes_gcm_parameters
+ SRCS
+ aes_gcm_parameters.cc
+ aes_gcm_parameters.h
+ DEPS
+ tink::aead::aead_parameters
+ absl::strings
+ tink::util::status
+ tink::util::statusor
+)
+
+tink_cc_library(
+ NAME aes_gcm_key
+ SRCS
+ aes_gcm_key.cc
+ aes_gcm_key.h
+ DEPS
+ tink::aead::aead_key
+ tink::aead::aes_gcm_parameters
+ absl::strings
+ absl::optional
+ tink::core::partial_key_access_token
+ tink::core::restricted_data
+ tink::subtle::subtle_util
+ tink::util::status
+ tink::util::statusor
+)
+
+tink_cc_library(
+ NAME aes_gcm_proto_serialization
+ SRCS
+ aes_gcm_proto_serialization.cc
+ aes_gcm_proto_serialization.h
+ DEPS
+ tink::aead::aes_gcm_key
+ tink::aead::aes_gcm_parameters
+ absl::status
+ absl::optional
+ tink::core::partial_key_access
+ tink::core::restricted_data
+ tink::core::secret_key_access_token
+ tink::internal::key_parser
+ tink::internal::key_serializer
+ tink::internal::mutable_serialization_registry
+ tink::internal::parameters_parser
+ tink::internal::parameters_serializer
+ tink::internal::proto_key_serialization
+ tink::internal::proto_parameters_serialization
+ tink::util::status
+ tink::util::statusor
+ tink::proto::aes_gcm_cc_proto
+ tink::proto::tink_cc_proto
)
# tests
@@ -351,10 +439,10 @@
tink::core::primitive_set
tink::core::registry
tink::config::tink_fips
+ tink::internal::fips_utils
tink::util::status
tink::util::statusor
tink::util::test_matchers
- tink::util::test_util
tink::proto::tink_cc_proto
)
@@ -487,11 +575,11 @@
gmock
absl::status
tink::core::aead
- tink::core::mac
tink::subtle::subtle
tink::subtle::aead_test_util
tink::subtle::aes_ctr_boringssl
tink::util::enums
+ tink::util::istream_input_stream
tink::util::secret_data
tink::util::status
tink::util::statusor
@@ -558,8 +646,11 @@
absl::endian
absl::memory
absl::status
+ absl::strings
tink::core::aead
+ tink::core::keyset_handle
tink::core::registry
+ tink::internal::ssl_util
tink::mac::mac_key_templates
tink::util::status
tink::util::statusor
@@ -573,6 +664,7 @@
SRCS
kms_envelope_aead_key_manager_test.cc
DEPS
+ tink::aead::aead_config
tink::aead::aead_key_templates
tink::aead::aes_eax_key_manager
tink::aead::kms_envelope_aead
@@ -584,7 +676,9 @@
tink::core::kms_client
tink::core::kms_clients
tink::core::registry
+ tink::mac::mac_key_templates
tink::subtle::aead_test_util
+ tink::util::fake_kms_client
tink::util::status
tink::util::statusor
tink::util::test_matchers
@@ -612,16 +706,6 @@
tink::proto::tink_cc_proto
)
-tink_cc_library(
- NAME failing_aead
- SRCS
- failing_aead.cc
- failing_aead.h
- DEPS
- absl::strings
- tink::core::aead
-)
-
tink_cc_test(
NAME failing_aead_test
SRCS
@@ -632,3 +716,50 @@
absl::status
tink::util::test_matchers
)
+
+tink_cc_test(
+ NAME aes_gcm_parameters_test
+ SRCS
+ aes_gcm_parameters_test.cc
+ DEPS
+ tink::aead::aes_gcm_parameters
+ gmock
+ tink::util::statusor
+ tink::util::test_matchers
+)
+
+tink_cc_test(
+ NAME aes_gcm_key_test
+ SRCS
+ aes_gcm_key_test.cc
+ DEPS
+ tink::aead::aes_gcm_key
+ tink::aead::aes_gcm_parameters
+ gmock
+ absl::optional
+ tink::core::partial_key_access
+ tink::core::restricted_data
+ tink::util::statusor
+ tink::util::test_matchers
+)
+
+tink_cc_test(
+ NAME aes_gcm_proto_serialization_test
+ SRCS
+ aes_gcm_proto_serialization_test.cc
+ DEPS
+ tink::aead::aes_gcm_key
+ tink::aead::aes_gcm_parameters
+ tink::aead::aes_gcm_proto_serialization
+ gmock
+ tink::core::insecure_secret_key_access
+ tink::core::partial_key_access
+ tink::core::restricted_data
+ tink::internal::mutable_serialization_registry
+ tink::internal::proto_key_serialization
+ tink::internal::proto_parameters_serialization
+ tink::subtle::random
+ tink::util::test_matchers
+ tink::proto::aes_gcm_cc_proto
+ tink::proto::tink_cc_proto
+)
diff --git a/cc/aead/aead_config.cc b/cc/aead/aead_config.cc
index d627064..b5a84ca 100644
--- a/cc/aead/aead_config.cc
+++ b/cc/aead/aead_config.cc
@@ -34,15 +34,6 @@
namespace crypto {
namespace tink {
-
-using ::google::crypto::tink::RegistryConfig;
-
-// static
-const RegistryConfig& AeadConfig::Latest() {
- static const RegistryConfig* config = new RegistryConfig();
- return *config;
-}
-
// static
util::Status AeadConfig::Register() {
auto status = MacConfig::Register();
diff --git a/cc/aead/aead_config.h b/cc/aead/aead_config.h
index 6431d64..08cb196 100644
--- a/cc/aead/aead_config.h
+++ b/cc/aead/aead_config.h
@@ -34,14 +34,6 @@
//
class AeadConfig {
public:
- static constexpr char kCatalogueName[] = "TinkAead";
- static constexpr char kPrimitiveName[] = "Aead";
-
- // Returns config of Aead implementations supported
- // in the current Tink release.
- ABSL_DEPRECATED("This is not supported anymore.")
- static const google::crypto::tink::RegistryConfig& Latest();
-
// Registers Aead primitive wrapper and key managers for all Aead key types
// from the current Tink release.
static crypto::tink::util::Status Register();
diff --git a/cc/aead/aead_config_test.cc b/cc/aead/aead_config_test.cc
index 726e3d1..4a7945d 100644
--- a/cc/aead/aead_config_test.cc
+++ b/cc/aead/aead_config_test.cc
@@ -29,27 +29,24 @@
#include "tink/aead/aead_key_templates.h"
#include "tink/aead/aes_gcm_key_manager.h"
#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
#include "tink/keyset_handle.h"
#include "tink/primitive_set.h"
#include "tink/registry.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "tink/util/test_matchers.h"
-#include "tink/util/test_util.h"
#include "proto/tink.pb.h"
namespace crypto {
namespace tink {
namespace {
-using ::crypto::tink::test::DummyAead;
using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::StatusIs;
-using ::google::crypto::tink::KeysetInfo;
-using ::google::crypto::tink::KeyStatusType;
+using ::crypto::tink::util::StatusOr;
using ::google::crypto::tink::KeyTemplate;
-using ::google::crypto::tink::OutputPrefixType;
-using ::testing::Eq;
+using ::testing::IsNull;
using ::testing::Not;
using ::testing::Test;
@@ -80,35 +77,17 @@
ASSERT_THAT(AeadConfig::Register(), IsOk());
- KeysetInfo::KeyInfo key_info;
- key_info.set_status(KeyStatusType::ENABLED);
- key_info.set_key_id(1234);
- key_info.set_output_prefix_type(OutputPrefixType::RAW);
- auto primitive_set = absl::make_unique<PrimitiveSet<Aead>>();
- ASSERT_THAT(primitive_set->set_primary(*primitive_set->AddPrimitive(
- absl::make_unique<DummyAead>("dummy"), key_info)),
- IsOk());
-
- util::StatusOr<std::unique_ptr<Aead>> primitive_result =
- Registry::Wrap(std::move(primitive_set));
-
- ASSERT_THAT(primitive_result, IsOk());
- util::StatusOr<std::string> encryption_result =
- (*primitive_result)->Encrypt("secret", "");
- ASSERT_THAT(encryption_result, IsOk());
-
- util::StatusOr<std::string> decryption_result =
- DummyAead("dummy").Decrypt(*encryption_result, "");
- ASSERT_THAT(decryption_result, IsOk());
- EXPECT_THAT(*decryption_result, Eq("secret"));
-
- decryption_result = DummyAead("dummy").Decrypt(*encryption_result, "wrong");
- EXPECT_THAT(decryption_result, Not(IsOk()));
+ StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+ KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+ ASSERT_THAT(keyset_handle.status(), IsOk());
+ StatusOr<std::unique_ptr<Aead>> aead = (*keyset_handle)->GetPrimitive<Aead>();
+ ASSERT_THAT(aead.status(), IsOk());
+ ASSERT_THAT(*aead, Not(IsNull()));
}
// FIPS-only mode tests
TEST_F(AeadConfigTest, RegisterNonFipsTemplates) {
- if (!IsFipsModeEnabled() || !FIPS_mode()) {
+ if (!IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto.";
}
@@ -128,7 +107,7 @@
}
TEST_F(AeadConfigTest, RegisterFipsValidTemplates) {
- if (!IsFipsModeEnabled() || !FIPS_mode()) {
+ if (!IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto.";
}
@@ -148,7 +127,7 @@
}
TEST_F(AeadConfigTest, RegisterFailsIfBoringCryptoNotAvailable) {
- if (!IsFipsModeEnabled() || FIPS_mode()) {
+ if (!IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
GTEST_SKIP()
<< "Only supported in FIPS-only mode with BoringCrypto not available.";
}
diff --git a/cc/aead/aead_key.h b/cc/aead/aead_key.h
new file mode 100644
index 0000000..869ddc8
--- /dev/null
+++ b/cc/aead/aead_key.h
@@ -0,0 +1,52 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AEAD_KEY_H_
+#define TINK_AEAD_AEAD_KEY_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/aead/aead_parameters.h"
+#include "tink/key.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents a function to encrypt and decrypt data using authenticated
+// encryption with associated data (AEAD).
+class AeadKey : public Key {
+ public:
+ // Returns the bytes prefixed to every ciphertext generated by this key.
+ //
+ // In order to make key rotation more efficient, Tink allows every AEAD key to
+ // have an associated ciphertext output prefix. When decrypting a ciphertext,
+ // only keys with a matching prefix have to be tried.
+ //
+ // Note that a priori, the output prefix may not be unique in a keyset
+ // (i.e., different keys in a keyset may have the same prefix or one prefix
+ // may be a prefix of another). To avoid this, built-in Tink keys use the
+ // convention that the prefix is either '0x00<big endian key id>' or
+ // '0x01<big endian key id>'.
+ virtual absl::string_view GetOutputPrefix() const = 0;
+
+ const AeadParameters& GetParameters() const override = 0;
+
+ bool operator==(const Key& other) const override = 0;
+};
+
+} // namespace tink
+} // namespace crypto
+
+#endif // TINK_AEAD_AEAD_KEY_H_
diff --git a/cc/aead/aead_parameters.h b/cc/aead/aead_parameters.h
new file mode 100644
index 0000000..dc5caad
--- /dev/null
+++ b/cc/aead/aead_parameters.h
@@ -0,0 +1,32 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AEAD_PARAMETERS_H_
+#define TINK_AEAD_AEAD_PARAMETERS_H_
+
+#include "tink/parameters.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes an `AeadKey` (e.g., key attributes), excluding the randomly chosen
+// key material.
+class AeadParameters : public Parameters {};
+
+} // namespace tink
+} // namespace crypto
+
+#endif // TINK_AEAD_AEAD_PARAMETERS_H_
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.cc b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
index ffe7867..10a0634 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
@@ -34,6 +34,7 @@
#include "tink/subtle/ind_cpa_cipher.h"
#include "tink/subtle/random.h"
#include "tink/util/enums.h"
+#include "tink/util/input_stream_util.h"
#include "tink/util/secret_data.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
@@ -52,12 +53,14 @@
constexpr int kMinTagSizeInBytes = 10;
}
-using crypto::tink::util::Enums;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
-using google::crypto::tink::AesCtrHmacAeadKey;
-using google::crypto::tink::AesCtrHmacAeadKeyFormat;
-using google::crypto::tink::HashType;
+using ::crypto::tink::util::Enums;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+using ::google::crypto::tink::AesCtrHmacAeadKey;
+using ::google::crypto::tink::AesCtrHmacAeadKeyFormat;
+using ::google::crypto::tink::AesCtrKey;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::HmacKey;
StatusOr<AesCtrHmacAeadKey> AesCtrHmacAeadKeyManager::CreateKey(
const AesCtrHmacAeadKeyFormat& aes_ctr_hmac_aead_key_format) const {
@@ -176,5 +179,53 @@
return HmacKeyManager().ValidateKeyFormat(key_format.hmac_key_format());
}
+// To ensure the resulting key can provide key commitment, the AES-CTR key must
+// be derived first, then the HMAC key. This avoids situation where it's
+// possible to brute force raw key material so that the 32th byte of the
+// keystream is a 0 Give party A a key with this raw key material, saying that
+// the size of the HMAC key is 32 bytes and the size of the AES key is 16 bytes.
+// Give party B a key with this raw key material, saying that the size of the
+// HMAC key is 31 bytes and the size of the AES key is 16 bytes. Since HMAC will
+// pad the key with zeroes, this leads to both parties using the same HMAC key,
+// but a different AES key (offset by 1 byte)
+StatusOr<AesCtrHmacAeadKey> AesCtrHmacAeadKeyManager::DeriveKey(
+ const AesCtrHmacAeadKeyFormat& key_format,
+ InputStream* input_stream) const {
+ Status status = ValidateKeyFormat(key_format);
+ if (!status.ok()) {
+ return status;
+ }
+ StatusOr<std::string> aes_ctr_randomness = ReadBytesFromStream(
+ key_format.aes_ctr_key_format().key_size(), input_stream);
+ if (!aes_ctr_randomness.ok()) {
+ if (absl::IsOutOfRange(aes_ctr_randomness.status())) {
+ return crypto::tink::util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "Could not get enough pseudorandomness from input stream");
+ }
+ return aes_ctr_randomness.status();
+ }
+ StatusOr<HmacKey> hmac_key =
+ HmacKeyManager().DeriveKey(key_format.hmac_key_format(), input_stream);
+ if (!hmac_key.ok()) {
+ return hmac_key.status();
+ }
+
+ google::crypto::tink::AesCtrHmacAeadKey key;
+ key.set_version(get_version());
+ *key.mutable_hmac_key() = hmac_key.value();
+
+ AesCtrKey* aes_ctr_key = key.mutable_aes_ctr_key();
+ aes_ctr_key->set_version(get_version());
+ aes_ctr_key->set_key_value(aes_ctr_randomness.value());
+ *aes_ctr_key->mutable_params() = key_format.aes_ctr_key_format().params();
+
+ status = ValidateKey(key);
+ if (!status.ok()) {
+ return status;
+ }
+ return key;
+}
+
} // namespace tink
} // namespace crypto
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.h b/cc/aead/aes_ctr_hmac_aead_key_manager.h
index a241a9c..e3b2f42 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.h
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.h
@@ -67,6 +67,10 @@
CreateKey(const google::crypto::tink::AesCtrHmacAeadKeyFormat& key_format)
const override;
+ crypto::tink::util::StatusOr<google::crypto::tink::AesCtrHmacAeadKey>
+ DeriveKey(const google::crypto::tink::AesCtrHmacAeadKeyFormat& key_format,
+ InputStream* input_stream) const override;
+
internal::FipsCompatibility FipsStatus() const override {
return internal::FipsCompatibility::kRequiresBoringCrypto;
}
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
index 6815ec3..b84711a 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
@@ -19,6 +19,7 @@
#include <stdint.h>
#include <memory>
+#include <sstream>
#include <string>
#include <utility>
@@ -26,13 +27,13 @@
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "tink/aead.h"
-#include "tink/mac.h"
#include "tink/subtle/aead_test_util.h"
#include "tink/subtle/aes_ctr_boringssl.h"
#include "tink/subtle/encrypt_then_authenticate.h"
#include "tink/subtle/hmac_boringssl.h"
#include "tink/subtle/ind_cpa_cipher.h"
#include "tink/util/enums.h"
+#include "tink/util/istream_input_stream.h"
#include "tink/util/secret_data.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
@@ -48,11 +49,11 @@
using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::StatusIs;
+using ::crypto::tink::util::IstreamInputStream;
using ::crypto::tink::util::StatusOr;
using ::google::crypto::tink::AesCtrHmacAeadKey;
using ::google::crypto::tink::AesCtrHmacAeadKeyFormat;
using ::google::crypto::tink::HashType;
-using ::google::crypto::tink::KeyData;
using ::testing::Eq;
using ::testing::Not;
using ::testing::SizeIs;
@@ -146,14 +147,24 @@
AesCtrHmacAeadKeyFormat key_format = CreateValidKeyFormat();
for (int len = 0; len < 42; ++len) {
key_format.mutable_aes_ctr_key_format()->set_key_size(len);
+ IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+ "0123456789abcde0123456789abcdefghijklmnopqrztuvwxyz0123456789abcde01"
+ "23456789abcdefghijklmnopqrztuvwxyz0123456789abcde0123456789abcdefghi"
+ "jklmnopqrztuvwxyz")};
if (len == 16 || len == 32) {
EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
IsOk())
<< "for length " << len;
+ EXPECT_THAT(
+ AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+ IsOk());
} else {
EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
Not(IsOk()))
<< "for length " << len;
+ EXPECT_THAT(
+ AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+ Not(IsOk()));
}
}
}
@@ -162,14 +173,24 @@
AesCtrHmacAeadKeyFormat key_format = CreateValidKeyFormat();
for (int len = 0; len < 42; ++len) {
key_format.mutable_hmac_key_format()->set_key_size(len);
+ IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+ "0123456789abcde0123456789abcdefghijklmnopqrztuvwxyz0123456789abcde01"
+ "23456789abcdefghijklmnopqrztuvwxyz0123456789abcde0123456789abcdefghi"
+ "jklmnopqrztuvwxyz")};
if (len >= 16) {
EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
IsOk())
<< "for length " << len;
+ EXPECT_THAT(
+ AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+ IsOk());
} else {
EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
Not(IsOk()))
<< "for length " << len;
+ EXPECT_THAT(
+ AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+ Not(IsOk()));
}
}
}
@@ -222,6 +243,92 @@
IsOk());
}
+TEST(AesCtrHmacAeadKeyManagerTest, Derive16ByteKey) {
+ AesCtrHmacAeadKeyFormat key_format;
+ key_format.mutable_aes_ctr_key_format()->set_key_size(16);
+ key_format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+ key_format.mutable_hmac_key_format()->set_key_size(16);
+ key_format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+ key_format.mutable_hmac_key_format()->mutable_params()->set_hash(
+ google::crypto::tink::SHA256);
+ key_format.mutable_hmac_key_format()->set_version(0);
+
+ IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+ "0123456789abcde_YELLOW_SUBMARINE_EXTRA")};
+
+ StatusOr<AesCtrHmacAeadKey> derived_key =
+ AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream);
+ ASSERT_THAT(derived_key, IsOk());
+ EXPECT_THAT(derived_key.value().aes_ctr_key().key_value(),
+ Eq("0123456789abcde_"));
+ EXPECT_THAT(derived_key.value().hmac_key().key_value(),
+ Eq("YELLOW_SUBMARINE"));
+ EXPECT_THAT(derived_key.value().hmac_key().params().hash(),
+ key_format.hmac_key_format().params().hash());
+ EXPECT_THAT(derived_key.value().hmac_key().params().tag_size(),
+ key_format.hmac_key_format().params().tag_size());
+ EXPECT_THAT(derived_key.value().aes_ctr_key().params().iv_size(),
+ Eq(key_format.aes_ctr_key_format().params().iv_size()));
+}
+
+TEST(AesCtrHmacAeadKeyManagerTest, Derive32ByteKey) {
+ AesCtrHmacAeadKeyFormat format;
+ format.mutable_aes_ctr_key_format()->set_key_size(32);
+ format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+ format.mutable_hmac_key_format()->set_key_size(32);
+ format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+ format.mutable_hmac_key_format()->mutable_params()->set_hash(
+ google::crypto::tink::SHA256);
+ format.mutable_hmac_key_format()->set_version(0);
+
+ IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+ "0123456789abcde0123456789abcdef_YELLOW_SUBMARINE_YELLOW_SUBMARIN")};
+
+ StatusOr<AesCtrHmacAeadKey> derived_key =
+ AesCtrHmacAeadKeyManager().DeriveKey(format, &input_stream);
+ ASSERT_THAT(derived_key, IsOk());
+ EXPECT_THAT(derived_key.value().aes_ctr_key().key_value(),
+ Eq("0123456789abcde0123456789abcdef_"));
+ EXPECT_THAT(derived_key.value().hmac_key().key_value(),
+ Eq("YELLOW_SUBMARINE_YELLOW_SUBMARIN"));
+}
+
+TEST(AesCtrHmacAeadKeyManagerTest, DeriveKeyNotEnoughRandomnessForAesCtrKey) {
+ AesCtrHmacAeadKeyFormat format;
+ format.mutable_aes_ctr_key_format()->set_key_size(32);
+ format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+ format.mutable_hmac_key_format()->set_key_size(32);
+ format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+ format.mutable_hmac_key_format()->mutable_params()->set_hash(
+ google::crypto::tink::SHA256);
+ format.mutable_hmac_key_format()->set_version(0);
+
+ IstreamInputStream input_stream{
+ absl::make_unique<std::stringstream>("0123456789")};
+
+ ASSERT_THAT(
+ AesCtrHmacAeadKeyManager().DeriveKey(format, &input_stream).status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesCtrHmacAeadKeyManagerTest, DeriveKeyNotEnoughRandomnessForHmacKey) {
+ AesCtrHmacAeadKeyFormat format;
+ format.mutable_aes_ctr_key_format()->set_key_size(16);
+ format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+ format.mutable_hmac_key_format()->set_key_size(32);
+ format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+ format.mutable_hmac_key_format()->mutable_params()->set_hash(
+ google::crypto::tink::SHA256);
+ format.mutable_hmac_key_format()->set_version(0);
+
+ IstreamInputStream input_stream{
+ absl::make_unique<std::stringstream>("YELLOW_SUBMARINE")};
+
+ ASSERT_THAT(
+ AesCtrHmacAeadKeyManager().DeriveKey(format, &input_stream).status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
} // namespace
} // namespace tink
} // namespace crypto
diff --git a/cc/aead/aes_gcm_key.cc b/cc/aead/aes_gcm_key.cc
new file mode 100644
index 0000000..4e93443
--- /dev/null
+++ b/cc/aead/aes_gcm_key.cc
@@ -0,0 +1,107 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aes_gcm_key.h"
+
+#include <string>
+
+#include "absl/strings/escaping.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/subtle_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::StatusOr<std::string> ComputeOutputPrefix(
+ const AesGcmParameters& parameters, absl::optional<int> id_requirement) {
+ switch (parameters.GetVariant()) {
+ case AesGcmParameters::Variant::kNoPrefix:
+ return std::string(""); // Empty prefix.
+ case AesGcmParameters::Variant::kCrunchy:
+ if (!id_requirement.has_value()) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "id requirement must have value with kCrunchy or kLegacy");
+ }
+ return absl::StrCat(absl::HexStringToBytes("00"),
+ subtle::BigEndian32(*id_requirement));
+ case AesGcmParameters::Variant::kTink:
+ if (!id_requirement.has_value()) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "id requirement must have value with kTink");
+ }
+ return absl::StrCat(absl::HexStringToBytes("01"),
+ subtle::BigEndian32(*id_requirement));
+ default:
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ absl::StrCat("Invalid variant: ", parameters.GetVariant()));
+ }
+}
+
+} // namespace
+
+util::StatusOr<AesGcmKey> AesGcmKey::Create(const AesGcmParameters& parameters,
+ const RestrictedData& key_bytes,
+ absl::optional<int> id_requirement,
+ PartialKeyAccessToken token) {
+ if (parameters.KeySizeInBytes() != key_bytes.size()) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Key size does not match AES-GCM parameters");
+ }
+ if (parameters.HasIdRequirement() && !id_requirement.has_value()) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "Cannot create key without ID requirement with parameters with ID "
+ "requirement");
+ }
+ if (!parameters.HasIdRequirement() && id_requirement.has_value()) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "Cannot create key with ID requirement with parameters without ID "
+ "requirement");
+ }
+ util::StatusOr<std::string> output_prefix =
+ ComputeOutputPrefix(parameters, id_requirement);
+ if (!output_prefix.ok()) {
+ return output_prefix.status();
+ }
+ return AesGcmKey(parameters, key_bytes, id_requirement,
+ *std::move(output_prefix));
+}
+
+bool AesGcmKey::operator==(const Key& other) const {
+ const AesGcmKey* that = dynamic_cast<const AesGcmKey*>(&other);
+ if (that == nullptr) {
+ return false;
+ }
+ if (GetParameters() != that->GetParameters()) {
+ return false;
+ }
+ if (id_requirement_ != that->id_requirement_) {
+ return false;
+ }
+ return key_bytes_ == that->key_bytes_;
+}
+
+} // namespace tink
+} // namespace crypto
diff --git a/cc/aead/aes_gcm_key.h b/cc/aead/aes_gcm_key.h
new file mode 100644
index 0000000..8159ea7
--- /dev/null
+++ b/cc/aead/aes_gcm_key.h
@@ -0,0 +1,84 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AES_GCM_KEY_H_
+#define TINK_AEAD_AES_GCM_KEY_H_
+
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aead_key.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents an AEAD that uses AES-GCM.
+class AesGcmKey : public AeadKey {
+ public:
+ // Copyable and movable.
+ AesGcmKey(const AesGcmKey& other) = default;
+ AesGcmKey& operator=(const AesGcmKey& other) = default;
+ AesGcmKey(AesGcmKey&& other) = default;
+ AesGcmKey& operator=(AesGcmKey&& other) = default;
+
+ // Creates a new AES-GCM key. If the parameters specify a variant that uses
+ // a prefix, then the id is used to compute this prefix.
+ static util::StatusOr<AesGcmKey> Create(const AesGcmParameters& parameters,
+ const RestrictedData& key_bytes,
+ absl::optional<int> id_requirement,
+ PartialKeyAccessToken token);
+
+ // Returns the underlying AES key.
+ util::StatusOr<RestrictedData> GetKeyBytes(
+ PartialKeyAccessToken token) const {
+ return key_bytes_;
+ }
+
+ absl::string_view GetOutputPrefix() const override { return output_prefix_; }
+
+ const AesGcmParameters& GetParameters() const override { return parameters_; }
+
+ absl::optional<int> GetIdRequirement() const override {
+ return id_requirement_;
+ }
+
+ bool operator==(const Key& other) const override;
+
+ private:
+ AesGcmKey(const AesGcmParameters& parameters, const RestrictedData& key_bytes,
+ absl::optional<int> id_requirement,
+ std::string output_prefix)
+ : parameters_(parameters),
+ key_bytes_(key_bytes),
+ id_requirement_(id_requirement),
+ output_prefix_(std::move(output_prefix)) {}
+
+ AesGcmParameters parameters_;
+ RestrictedData key_bytes_;
+ absl::optional<int> id_requirement_;
+ std::string output_prefix_;
+};
+
+} // namespace tink
+} // namespace crypto
+
+#endif // TINK_AEAD_AES_GCM_KEY_H_
diff --git a/cc/aead/aes_gcm_key_test.cc b/cc/aead/aes_gcm_key_test.cc
new file mode 100644
index 0000000..06a0a35
--- /dev/null
+++ b/cc/aead/aes_gcm_key_test.cc
@@ -0,0 +1,285 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aes_gcm_key.h"
+
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+ AesGcmParameters::Variant variant;
+ absl::optional<int> id_requirement;
+ std::string output_prefix;
+};
+
+using AesGcmKeyTest = TestWithParam<std::tuple<int, int, TestCase>>;
+
+INSTANTIATE_TEST_SUITE_P(
+ AesGcmKeyTestSuite, AesGcmKeyTest,
+ Combine(Values(16, 24, 32), Range(12, 16),
+ Values(TestCase{AesGcmParameters::Variant::kTink, 0x02030400,
+ std::string("\x01\x02\x03\x04\x00", 5)},
+ TestCase{AesGcmParameters::Variant::kCrunchy, 0x01030005,
+ std::string("\x00\x01\x03\x00\x05", 5)},
+ TestCase{AesGcmParameters::Variant::kNoPrefix, absl::nullopt,
+ ""})));
+
+TEST_P(AesGcmKeyTest, CreateSucceeds) {
+ int key_size;
+ int iv_and_tag_size; // NOTE: There's no requirement for IV size == tag size.
+ TestCase test_case;
+ std::tie(key_size, iv_and_tag_size, test_case) = GetParam();
+
+ util::StatusOr<AesGcmParameters> params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(key_size)
+ .SetIvSizeInBytes(iv_and_tag_size)
+ .SetTagSizeInBytes(iv_and_tag_size)
+ .SetVariant(test_case.variant)
+ .Build();
+ ASSERT_THAT(params, IsOk());
+
+ RestrictedData secret = RestrictedData(key_size);
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ EXPECT_THAT(key->GetParameters(), Eq(*params));
+ EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement));
+ EXPECT_THAT(key->GetOutputPrefix(), Eq(test_case.output_prefix));
+}
+
+TEST(AesGcmKeyTest, CreateKeyWithMismatchedKeySizeFails) {
+ // Key size parameter is 32 bytes.
+ util::StatusOr<AesGcmParameters> params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(params, IsOk());
+
+ // Key material is 16 bytes (another valid key length).
+ RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/16);
+
+ EXPECT_THAT(AesGcmKey::Create(*params, mismatched_secret,
+ /*id_requirement=*/123, GetPartialKeyAccess())
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmKeyTest, CreateKeyWithInvalidIdRequirementFails) {
+ util::StatusOr<AesGcmParameters> no_prefix_params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build();
+ ASSERT_THAT(no_prefix_params, IsOk());
+
+ util::StatusOr<AesGcmParameters> tink_params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(tink_params, IsOk());
+
+ RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+ EXPECT_THAT(AesGcmKey::Create(*no_prefix_params, secret,
+ /*id_requirement=*/123, GetPartialKeyAccess())
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ EXPECT_THAT(
+ AesGcmKey::Create(*tink_params, secret,
+ /*id_requirement=*/absl::nullopt, GetPartialKeyAccess())
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmKeyTest, GetKeyBytes) {
+ int key_size;
+ int iv_and_tag_size; // NOTE: There's no requirement for IV size == tag size.
+ TestCase test_case;
+ std::tie(key_size, iv_and_tag_size, test_case) = GetParam();
+
+ util::StatusOr<AesGcmParameters> params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(key_size)
+ .SetIvSizeInBytes(iv_and_tag_size)
+ .SetTagSizeInBytes(iv_and_tag_size)
+ .SetVariant(test_case.variant)
+ .Build();
+ ASSERT_THAT(params, IsOk());
+
+ RestrictedData secret = RestrictedData(key_size);
+
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret));
+}
+
+TEST_P(AesGcmKeyTest, KeyEquals) {
+ int key_size;
+ int iv_and_tag_size; // NOTE: There's no requirement for IV size == tag size.
+ TestCase test_case;
+ std::tie(key_size, iv_and_tag_size, test_case) = GetParam();
+
+ util::StatusOr<AesGcmParameters> params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(key_size)
+ .SetIvSizeInBytes(iv_and_tag_size)
+ .SetTagSizeInBytes(iv_and_tag_size)
+ .SetVariant(test_case.variant)
+ .Build();
+ ASSERT_THAT(params, IsOk());
+
+ RestrictedData secret = RestrictedData(key_size);
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create(
+ *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+ ASSERT_THAT(other_key, IsOk());
+
+ EXPECT_TRUE(*key == *other_key);
+ EXPECT_TRUE(*other_key == *key);
+ EXPECT_FALSE(*key != *other_key);
+ EXPECT_FALSE(*other_key != *key);
+}
+
+TEST(AesGcmKeyTest, DifferentVariantNotEqual) {
+ util::StatusOr<AesGcmParameters> crunchy_params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kCrunchy)
+ .Build();
+ ASSERT_THAT(crunchy_params, IsOk());
+
+ util::StatusOr<AesGcmParameters> tink_params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(tink_params, IsOk());
+
+ RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+ util::StatusOr<AesGcmKey> key =
+ AesGcmKey::Create(*crunchy_params, secret, /*id_requirement=*/0x01020304,
+ GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<AesGcmKey> other_key =
+ AesGcmKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304,
+ GetPartialKeyAccess());
+ ASSERT_THAT(other_key, IsOk());
+
+ EXPECT_TRUE(*key != *other_key);
+ EXPECT_TRUE(*other_key != *key);
+ EXPECT_FALSE(*key == *other_key);
+ EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesGcmKeyTest, DifferentSecretDataNotEqual) {
+ util::StatusOr<AesGcmParameters> params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(params, IsOk());
+
+ RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/32);
+ RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/32);
+
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create(
+ *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+ ASSERT_THAT(other_key, IsOk());
+
+ EXPECT_TRUE(*key != *other_key);
+ EXPECT_TRUE(*other_key != *key);
+ EXPECT_FALSE(*key == *other_key);
+ EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesGcmKeyTest, DifferentIdRequirementNotEqual) {
+ util::StatusOr<AesGcmParameters> params =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(params, IsOk());
+
+ RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create(
+ *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess());
+ ASSERT_THAT(other_key, IsOk());
+
+ EXPECT_TRUE(*key != *other_key);
+ EXPECT_TRUE(*other_key != *key);
+ EXPECT_FALSE(*key == *other_key);
+ EXPECT_FALSE(*other_key == *key);
+}
+
+} // namespace
+} // namespace tink
+} // namespace crypto
diff --git a/cc/aead/aes_gcm_parameters.cc b/cc/aead/aes_gcm_parameters.cc
new file mode 100644
index 0000000..d78d888
--- /dev/null
+++ b/cc/aead/aes_gcm_parameters.cc
@@ -0,0 +1,103 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aes_gcm_parameters.h"
+
+#include <set>
+
+#include "absl/strings/str_cat.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetKeySizeInBytes(
+ int key_size) {
+ key_size_in_bytes_ = key_size;
+ return *this;
+}
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetIvSizeInBytes(
+ int iv_size) {
+ iv_size_in_bytes_ = iv_size;
+ return *this;
+}
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetTagSizeInBytes(
+ int tag_size) {
+ tag_size_in_bytes_ = tag_size;
+ return *this;
+}
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetVariant(
+ Variant variant) {
+ variant_ = variant;
+ return *this;
+}
+
+util::StatusOr<AesGcmParameters> AesGcmParameters::Builder::Build() {
+ if (key_size_in_bytes_ != 16 && key_size_in_bytes_ != 24 &&
+ key_size_in_bytes_ != 32) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ absl::StrCat("Key size should be 16, 24, or 32 bytes, got ",
+ key_size_in_bytes_, " bytes."));
+ }
+ if (iv_size_in_bytes_ <= 0) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ absl::StrCat("IV size should be positive, got ",
+ iv_size_in_bytes_, " bytes."));
+ }
+ if (tag_size_in_bytes_ < 12 || tag_size_in_bytes_ > 16) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ absl::StrCat("Tag size should be between 12 and 16 bytes, got ",
+ tag_size_in_bytes_, " bytes."));
+ }
+ static const std::set<Variant>* supported_variants = new std::set<Variant>(
+ {Variant::kTink, Variant::kCrunchy, Variant::kNoPrefix});
+ if (supported_variants->find(variant_) == supported_variants->end()) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "Cannot create AES-GCM parameters with unknown variant.");
+ }
+ return AesGcmParameters(key_size_in_bytes_, iv_size_in_bytes_,
+ tag_size_in_bytes_, variant_);
+}
+
+bool AesGcmParameters::operator==(const Parameters& other) const {
+ const AesGcmParameters* that = dynamic_cast<const AesGcmParameters*>(&other);
+ if (that == nullptr) {
+ return false;
+ }
+ if (key_size_in_bytes_ != that->key_size_in_bytes_) {
+ return false;
+ }
+ if (iv_size_in_bytes_ != that->iv_size_in_bytes_) {
+ return false;
+ }
+ if (tag_size_in_bytes_ != that->tag_size_in_bytes_) {
+ return false;
+ }
+ if (variant_ != that->variant_) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace tink
+} // namespace crypto
diff --git a/cc/aead/aes_gcm_parameters.h b/cc/aead/aes_gcm_parameters.h
new file mode 100644
index 0000000..de87b2c
--- /dev/null
+++ b/cc/aead/aes_gcm_parameters.h
@@ -0,0 +1,105 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AES_GCM_PARAMETERS_H_
+#define TINK_AEAD_AES_GCM_PARAMETERS_H_
+
+#include "tink/aead/aead_parameters.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes the parameters of an `AesGcmKey`.
+class AesGcmParameters : public AeadParameters {
+ public:
+ // Description of the output prefix prepended to the ciphertext.
+ enum class Variant : int {
+ // Prepends '0x01<big endian key id>' to the ciphertext.
+ kTink = 1,
+ // Prepends '0x00<big endian key id>' to the ciphertext.
+ kCrunchy = 2,
+ // Does not prepend any prefix (i.e., keys must have no ID requirement).
+ kNoPrefix = 3,
+ // Added to guard from failures that may be caused by future expansions.
+ kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+ };
+
+ // Creates AES-GCM parameters instances.
+ class Builder {
+ public:
+ // Copyable and movable.
+ Builder(const Builder& other) = default;
+ Builder& operator=(const Builder& other) = default;
+ Builder(Builder&& other) = default;
+ Builder& operator=(Builder&& other) = default;
+
+ // Creates initially empty parameters builder.
+ Builder() = default;
+
+ Builder& SetKeySizeInBytes(int key_size);
+ Builder& SetIvSizeInBytes(int iv_size);
+ Builder& SetTagSizeInBytes(int tag_size);
+ Builder& SetVariant(Variant variant);
+
+ // Creates AES-GCM parameters object from this builder.
+ util::StatusOr<AesGcmParameters> Build();
+
+ private:
+ int key_size_in_bytes_;
+ int iv_size_in_bytes_;
+ int tag_size_in_bytes_;
+ Variant variant_;
+ };
+
+ // Copyable and movable.
+ AesGcmParameters(const AesGcmParameters& other) = default;
+ AesGcmParameters& operator=(const AesGcmParameters& other) = default;
+ AesGcmParameters(AesGcmParameters&& other) = default;
+ AesGcmParameters& operator=(AesGcmParameters&& other) = default;
+
+ int KeySizeInBytes() const { return key_size_in_bytes_; }
+
+ int IvSizeInBytes() const { return iv_size_in_bytes_; }
+
+ int TagSizeInBytes() const { return tag_size_in_bytes_; }
+
+ Variant GetVariant() const { return variant_; }
+
+ bool HasIdRequirement() const override {
+ return variant_ != Variant::kNoPrefix;
+ }
+
+ bool operator==(const Parameters& other) const override;
+
+ private:
+ AesGcmParameters(int key_size_in_bytes, int iv_size_in_bytes,
+ int tag_size_in_bytes, Variant variant)
+ : key_size_in_bytes_(key_size_in_bytes),
+ iv_size_in_bytes_(iv_size_in_bytes),
+ tag_size_in_bytes_(tag_size_in_bytes),
+ variant_(variant) {}
+
+ int key_size_in_bytes_;
+ int iv_size_in_bytes_;
+ int tag_size_in_bytes_;
+ Variant variant_;
+};
+
+} // namespace tink
+} // namespace crypto
+
+#endif // TINK_AEAD_AES_GCM_PARAMETERS_H_
diff --git a/cc/aead/aes_gcm_parameters_test.cc b/cc/aead/aes_gcm_parameters_test.cc
new file mode 100644
index 0000000..7238bb9
--- /dev/null
+++ b/cc/aead/aes_gcm_parameters_test.cc
@@ -0,0 +1,386 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aes_gcm_parameters.h"
+
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct BuildTestCase {
+ AesGcmParameters::Variant variant;
+ int key_size;
+ int iv_size;
+ int tag_size;
+ bool has_id_requirement;
+};
+
+using AesGcmParametersBuildTest = TestWithParam<BuildTestCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+ AesGcmParametersBuildTestSuite, AesGcmParametersBuildTest,
+ Values(BuildTestCase{AesGcmParameters::Variant::kTink, /*key_size=*/16,
+ /*iv_size=*/12, /*tag_size=*/12,
+ /*has_id_requirement=*/true},
+ BuildTestCase{AesGcmParameters::Variant::kCrunchy, /*key_size=*/24,
+ /*iv_size=*/14, /*tag_size=*/14,
+ /*has_id_requirement=*/true},
+ BuildTestCase{AesGcmParameters::Variant::kNoPrefix,
+ /*key_size=*/32, /*iv_size=*/16, /*tag_size=*/16,
+ /*has_id_requirement=*/false}));
+
+TEST_P(AesGcmParametersBuildTest, Build) {
+ BuildTestCase test_case = GetParam();
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(test_case.key_size)
+ .SetIvSizeInBytes(test_case.iv_size)
+ .SetTagSizeInBytes(test_case.tag_size)
+ .SetVariant(test_case.variant)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ EXPECT_THAT(parameters->KeySizeInBytes(), Eq(test_case.key_size));
+ EXPECT_THAT(parameters->IvSizeInBytes(), Eq(test_case.iv_size));
+ EXPECT_THAT(parameters->TagSizeInBytes(), Eq(test_case.tag_size));
+ EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant));
+ EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingVariantFails) {
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidVariantFails) {
+ EXPECT_THAT(
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::
+ kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingKeySizeFails) {
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidKeySizeFails) {
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(15)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(17)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(23)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(25)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(31)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(33)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingIvSizeFails) {
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidIvSizeFails) {
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(0)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingTagSizeFails) {
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidTagSizeFails) {
+ // Too small.
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(11)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+ // Too big.
+ EXPECT_THAT(AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(17)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build()
+ .status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, CopyConstructor) {
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ AesGcmParameters copy(*parameters);
+ EXPECT_THAT(copy.KeySizeInBytes(), Eq(16));
+ EXPECT_THAT(copy.IvSizeInBytes(), Eq(16));
+ EXPECT_THAT(copy.TagSizeInBytes(), Eq(16));
+ EXPECT_THAT(copy.GetVariant(), Eq(AesGcmParameters::Variant::kTink));
+ EXPECT_THAT(copy.HasIdRequirement(), IsTrue());
+}
+
+TEST(AesGcmParametersTest, CopyAssignment) {
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ AesGcmParameters copy = *parameters;
+ EXPECT_THAT(copy.KeySizeInBytes(), Eq(16));
+ EXPECT_THAT(copy.IvSizeInBytes(), Eq(16));
+ EXPECT_THAT(copy.TagSizeInBytes(), Eq(16));
+ EXPECT_THAT(copy.GetVariant(), Eq(AesGcmParameters::Variant::kTink));
+ EXPECT_THAT(copy.HasIdRequirement(), IsTrue());
+}
+
+using AesGcmParametersVariantTest =
+ TestWithParam<std::tuple<int, int, AesGcmParameters::Variant>>;
+
+INSTANTIATE_TEST_SUITE_P(AesGcmParametersVariantTestSuite,
+ AesGcmParametersVariantTest,
+ Combine(Values(16, 24, 32), Range(12, 16),
+ Values(AesGcmParameters::Variant::kTink,
+ AesGcmParameters::Variant::kCrunchy,
+ AesGcmParameters::Variant::kNoPrefix)));
+
+TEST_P(AesGcmParametersVariantTest, ParametersEquals) {
+ int key_size;
+ int iv_and_tag_size;
+ AesGcmParameters::Variant variant;
+ std::tie(key_size, iv_and_tag_size, variant) = GetParam();
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(key_size)
+ .SetIvSizeInBytes(iv_and_tag_size)
+ .SetTagSizeInBytes(iv_and_tag_size)
+ .SetVariant(variant)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<AesGcmParameters> other_parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(key_size)
+ .SetIvSizeInBytes(iv_and_tag_size)
+ .SetTagSizeInBytes(iv_and_tag_size)
+ .SetVariant(variant)
+ .Build();
+ ASSERT_THAT(other_parameters, IsOk());
+
+ EXPECT_TRUE(*parameters == *other_parameters);
+ EXPECT_TRUE(*other_parameters == *parameters);
+ EXPECT_FALSE(*parameters != *other_parameters);
+ EXPECT_FALSE(*other_parameters != *parameters);
+}
+
+TEST(AesGcmParametersTest, KeySizeNotEqual) {
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<AesGcmParameters> other_parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(24)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(other_parameters, IsOk());
+
+ EXPECT_TRUE(*parameters != *other_parameters);
+ EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesGcmParametersTest, IvSizeNotEqual) {
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<AesGcmParameters> other_parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(12)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(other_parameters, IsOk());
+
+ EXPECT_TRUE(*parameters != *other_parameters);
+ EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesGcmParametersTest, TagSizeNotEqual) {
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<AesGcmParameters> other_parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(14)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(other_parameters, IsOk());
+
+ EXPECT_TRUE(*parameters != *other_parameters);
+ EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesGcmParametersTest, VariantNotEqual) {
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kTink)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<AesGcmParameters> other_parameters =
+ AesGcmParameters::Builder()
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(16)
+ .SetTagSizeInBytes(16)
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .Build();
+ ASSERT_THAT(other_parameters, IsOk());
+
+ EXPECT_TRUE(*parameters != *other_parameters);
+ EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+} // namespace
+} // namespace tink
+} // namespace crypto
diff --git a/cc/aead/aes_gcm_proto_serialization.cc b/cc/aead/aes_gcm_proto_serialization.cc
new file mode 100644
index 0000000..57272ff
--- /dev/null
+++ b/cc/aead/aes_gcm_proto_serialization.cc
@@ -0,0 +1,273 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aes_gcm_proto_serialization.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aes_gcm_key.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::OutputPrefixType;
+
+using AesGcmProtoParametersParserImpl =
+ internal::ParametersParserImpl<internal::ProtoParametersSerialization,
+ AesGcmParameters>;
+using AesGcmProtoParametersSerializerImpl =
+ internal::ParametersSerializerImpl<AesGcmParameters,
+ internal::ProtoParametersSerialization>;
+using AesGcmProtoKeyParserImpl =
+ internal::KeyParserImpl<internal::ProtoKeySerialization, AesGcmKey>;
+using AesGcmProtoKeySerializerImpl =
+ internal::KeySerializerImpl<AesGcmKey, internal::ProtoKeySerialization>;
+
+const absl::string_view kTypeUrl =
+ "type.googleapis.com/google.crypto.tink.AesGcmKey";
+
+util::StatusOr<AesGcmParameters::Variant> ToVariant(
+ OutputPrefixType output_prefix_type) {
+ switch (output_prefix_type) {
+ case OutputPrefixType::LEGACY:
+ ABSL_FALLTHROUGH_INTENDED; // Parse LEGACY output prefix as CRUNCHY.
+ case OutputPrefixType::CRUNCHY:
+ return AesGcmParameters::Variant::kCrunchy;
+ case OutputPrefixType::RAW:
+ return AesGcmParameters::Variant::kNoPrefix;
+ case OutputPrefixType::TINK:
+ return AesGcmParameters::Variant::kTink;
+ default:
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Could not determine AesGcmParameters::Variant");
+ }
+}
+
+util::StatusOr<OutputPrefixType> ToOutputPrefixType(
+ AesGcmParameters::Variant variant) {
+ switch (variant) {
+ case AesGcmParameters::Variant::kCrunchy:
+ return OutputPrefixType::CRUNCHY;
+ case AesGcmParameters::Variant::kNoPrefix:
+ return OutputPrefixType::RAW;
+ case AesGcmParameters::Variant::kTink:
+ return OutputPrefixType::TINK;
+ default:
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Could not determine output prefix type");
+ }
+}
+
+// Legacy Tink AES-GCM key proto format assumes 12-byte random IVs and 16-byte
+// tags.
+util::Status ValidateParamsForProto(const AesGcmParameters& params) {
+ if (params.IvSizeInBytes() != 12) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "Tink currently restricts AES-GCM IV size to 12 bytes.");
+ }
+ if (params.TagSizeInBytes() != 16) {
+ return util::Status(
+ absl::StatusCode::kInvalidArgument,
+ "Tink currently restricts AES-GCM tag size to 16 bytes.");
+ }
+ return util::OkStatus();
+}
+
+util::StatusOr<AesGcmParameters> ParseParameters(
+ const internal::ProtoParametersSerialization& serialization) {
+ if (serialization.GetKeyTemplate().type_url() != kTypeUrl) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Wrong type URL when parsing AesGcmParameters.");
+ }
+
+ AesGcmKeyFormat proto_key_format;
+ if (!proto_key_format.ParseFromString(
+ serialization.GetKeyTemplate().value())) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Failed to parse AesGcmKeyFormat proto");
+ }
+ if (proto_key_format.version() != 0) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Only version 0 keys are accepted.");
+ }
+
+ util::StatusOr<AesGcmParameters::Variant> variant =
+ ToVariant(serialization.GetKeyTemplate().output_prefix_type());
+ if (!variant.ok()) return variant.status();
+
+ // Legacy Tink AES-GCM key proto format assumes 12-byte random IVs and 16-byte
+ // tags.
+ return AesGcmParameters::Builder()
+ .SetVariant(*variant)
+ .SetKeySizeInBytes(proto_key_format.key_size())
+ .SetIvSizeInBytes(12)
+ .SetTagSizeInBytes(16)
+ .Build();
+}
+
+util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters(
+ const AesGcmParameters& parameters) {
+ util::Status valid_params = ValidateParamsForProto(parameters);
+ if (!valid_params.ok()) return valid_params;
+
+ util::StatusOr<OutputPrefixType> output_prefix_type =
+ ToOutputPrefixType(parameters.GetVariant());
+ if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+ AesGcmKeyFormat proto_key_format;
+ proto_key_format.set_key_size(parameters.KeySizeInBytes());
+
+ return internal::ProtoParametersSerialization::Create(
+ kTypeUrl, *output_prefix_type, proto_key_format.SerializeAsString());
+}
+
+util::StatusOr<AesGcmKey> ParseKey(
+ const internal::ProtoKeySerialization& serialization,
+ absl::optional<SecretKeyAccessToken> token) {
+ if (serialization.TypeUrl() != kTypeUrl) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Wrong type URL when parsing AesGcmKey.");
+ }
+ if (!token.has_value()) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "SecretKeyAccess is required");
+ }
+ google::crypto::tink::AesGcmKey proto_key;
+ RestrictedData restricted_data = serialization.SerializedKeyProto();
+ // OSS proto library complains if input is not converted to a string.
+ if (!proto_key.ParseFromString(
+ std::string(restricted_data.GetSecret(*token)))) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Failed to parse AesGcmKey proto");
+ }
+ if (proto_key.version() != 0) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "Only version 0 keys are accepted.");
+ }
+
+ util::StatusOr<AesGcmParameters::Variant> variant =
+ ToVariant(serialization.GetOutputPrefixType());
+ if (!variant.ok()) return variant.status();
+
+ // Legacy AES-GCM key proto format assumes 12-byte random IVs and 16-byte
+ // tags.
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(*variant)
+ .SetKeySizeInBytes(proto_key.key_value().length())
+ .SetIvSizeInBytes(12)
+ .SetTagSizeInBytes(16)
+ .Build();
+ if (!parameters.ok()) return parameters.status();
+
+ return AesGcmKey::Create(
+ *parameters, RestrictedData(proto_key.key_value(), *token),
+ serialization.IdRequirement(), GetPartialKeyAccess());
+}
+
+util::StatusOr<internal::ProtoKeySerialization> SerializeKey(
+ const AesGcmKey& key, absl::optional<SecretKeyAccessToken> token) {
+ util::Status valid_params = ValidateParamsForProto(key.GetParameters());
+ if (!valid_params.ok()) return valid_params;
+
+ util::StatusOr<RestrictedData> restricted_input =
+ key.GetKeyBytes(GetPartialKeyAccess());
+ if (!restricted_input.ok()) return restricted_input.status();
+ if (!token.has_value()) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "SecretKeyAccess is required");
+ }
+
+ google::crypto::tink::AesGcmKey proto_key;
+ proto_key.set_version(0);
+ // OSS proto library complains if input is not converted to a string.
+ proto_key.set_key_value(std::string(restricted_input->GetSecret(*token)));
+
+ util::StatusOr<OutputPrefixType> output_prefix_type =
+ ToOutputPrefixType(key.GetParameters().GetVariant());
+ if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+ RestrictedData restricted_output =
+ RestrictedData(proto_key.SerializeAsString(), *token);
+ return internal::ProtoKeySerialization::Create(
+ kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC,
+ *output_prefix_type, key.GetIdRequirement());
+}
+
+AesGcmProtoParametersParserImpl* AesGcmProtoParametersParser() {
+ static auto* parser =
+ new AesGcmProtoParametersParserImpl(kTypeUrl, ParseParameters);
+ return parser;
+}
+
+AesGcmProtoParametersSerializerImpl* AesGcmProtoParametersSerializer() {
+ static auto* serializer =
+ new AesGcmProtoParametersSerializerImpl(kTypeUrl, SerializeParameters);
+ return serializer;
+}
+
+AesGcmProtoKeyParserImpl* AesGcmProtoKeyParser() {
+ static auto* parser = new AesGcmProtoKeyParserImpl(kTypeUrl, ParseKey);
+ return parser;
+}
+
+AesGcmProtoKeySerializerImpl* AesGcmProtoKeySerializer() {
+ static auto* serializer = new AesGcmProtoKeySerializerImpl(SerializeKey);
+ return serializer;
+}
+
+} // namespace
+
+util::Status RegisterAesGcmProtoSerialization() {
+ util::Status status =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .RegisterParametersParser(AesGcmProtoParametersParser());
+ if (!status.ok()) return status;
+
+ status = internal::MutableSerializationRegistry::GlobalInstance()
+ .RegisterParametersSerializer(AesGcmProtoParametersSerializer());
+ if (!status.ok()) return status;
+
+ status = internal::MutableSerializationRegistry::GlobalInstance()
+ .RegisterKeyParser(AesGcmProtoKeyParser());
+ if (!status.ok()) return status;
+
+ return internal::MutableSerializationRegistry::GlobalInstance()
+ .RegisterKeySerializer(AesGcmProtoKeySerializer());
+}
+
+} // namespace tink
+} // namespace crypto
diff --git a/cc/aead/aes_gcm_proto_serialization.h b/cc/aead/aes_gcm_proto_serialization.h
new file mode 100644
index 0000000..68a49f4
--- /dev/null
+++ b/cc/aead/aes_gcm_proto_serialization.h
@@ -0,0 +1,31 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AES_GCM_PROTO_SERIALIZATION_H_
+#define TINK_AEAD_AES_GCM_PROTO_SERIALIZATION_H_
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+// Registers proto parsers and serializers for AES-GCM parameters and keys.
+crypto::tink::util::Status RegisterAesGcmProtoSerialization();
+
+} // namespace tink
+} // namespace crypto
+
+#endif // TINK_AEAD_AES_GCM_PROTO_SERIALIZATION_H_
diff --git a/cc/aead/aes_gcm_proto_serialization_test.cc b/cc/aead/aes_gcm_proto_serialization_test.cc
new file mode 100644
index 0000000..f19c820
--- /dev/null
+++ b/cc/aead/aes_gcm_proto_serialization_test.cc
@@ -0,0 +1,515 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aes_gcm_proto_serialization.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aes_gcm_key.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::subtle::Random;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+ AesGcmParameters::Variant variant;
+ OutputPrefixType output_prefix_type;
+ int key_size;
+ int iv_size;
+ int tag_size;
+ absl::optional<int> id;
+ std::string output_prefix;
+};
+
+class AesGcmProtoSerializationTest : public TestWithParam<TestCase> {
+ protected:
+ void SetUp() override {
+ internal::MutableSerializationRegistry::GlobalInstance().Reset();
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ AesGcmProtoSerializationTestSuite, AesGcmProtoSerializationTest,
+ Values(TestCase{AesGcmParameters::Variant::kTink, OutputPrefixType::TINK,
+ /*key_size=*/16, /*iv_size=*/12, /*tag_size=*/16,
+ /*id=*/0x02030400,
+ /*output_prefix=*/std::string("\x01\x02\x03\x04\x00", 5)},
+ TestCase{AesGcmParameters::Variant::kCrunchy,
+ OutputPrefixType::CRUNCHY, /*key_size=*/16, /*iv_size=*/12,
+ /*tag_size=*/16, /*id=*/0x01030005,
+ /*output_prefix=*/std::string("\x00\x01\x03\x00\x05", 5)},
+ TestCase{AesGcmParameters::Variant::kNoPrefix, OutputPrefixType::RAW,
+ /*key_size=*/32, /*iv_size=*/12, /*tag_size=*/16,
+ /*id=*/absl::nullopt, /*output_prefix=*/""}));
+
+TEST_P(AesGcmProtoSerializationTest, ParseParameters) {
+ TestCase test_case = GetParam();
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ AesGcmKeyFormat key_format_proto;
+ key_format_proto.set_version(0);
+ key_format_proto.set_key_size(test_case.key_size);
+
+ util::StatusOr<internal::ProtoParametersSerialization> serialization =
+ internal::ProtoParametersSerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey",
+ test_case.output_prefix_type, key_format_proto.SerializeAsString());
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Parameters>> params =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+ *serialization);
+ ASSERT_THAT(params, IsOk());
+ EXPECT_THAT((*params)->HasIdRequirement(), test_case.id.has_value());
+
+ const AesGcmParameters* gcm_params =
+ dynamic_cast<const AesGcmParameters*>(params->get());
+ ASSERT_THAT(gcm_params, NotNull());
+ EXPECT_THAT(gcm_params->GetVariant(), Eq(test_case.variant));
+ EXPECT_THAT(gcm_params->KeySizeInBytes(), Eq(test_case.key_size));
+ EXPECT_THAT(gcm_params->IvSizeInBytes(), Eq(test_case.iv_size));
+ EXPECT_THAT(gcm_params->TagSizeInBytes(), Eq(test_case.tag_size));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseParametersWithInvalidSerialization) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ AesGcmKeyFormat key_format_proto;
+ key_format_proto.set_version(0);
+ key_format_proto.set_key_size(16);
+
+ util::StatusOr<internal::ProtoParametersSerialization> serialization =
+ internal::ProtoParametersSerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey",
+ OutputPrefixType::RAW, "invalid_serialization");
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Parameters>> params =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+ *serialization);
+ EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseParametersWithUnkownOutputPrefix) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ AesGcmKeyFormat key_format_proto;
+ key_format_proto.set_version(0);
+ key_format_proto.set_key_size(16);
+
+ util::StatusOr<internal::ProtoParametersSerialization> serialization =
+ internal::ProtoParametersSerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey",
+ OutputPrefixType::UNKNOWN_PREFIX,
+ key_format_proto.SerializeAsString());
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Parameters>> params =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+ *serialization);
+ EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseParametersWithInvalidVersion) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ AesGcmKeyFormat key_format_proto;
+ key_format_proto.set_version(1);
+ key_format_proto.set_key_size(16);
+
+ util::StatusOr<internal::ProtoParametersSerialization> serialization =
+ internal::ProtoParametersSerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey",
+ OutputPrefixType::RAW,
+ key_format_proto.SerializeAsString());
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Parameters>> params =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+ *serialization);
+ EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmProtoSerializationTest, SerializeParameters) {
+ TestCase test_case = GetParam();
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(test_case.variant)
+ .SetKeySizeInBytes(test_case.key_size)
+ .SetIvSizeInBytes(test_case.iv_size)
+ .SetTagSizeInBytes(test_case.tag_size)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeParameters<internal::ProtoParametersSerialization>(
+ *parameters);
+ ASSERT_THAT(serialization, IsOk());
+ EXPECT_THAT((*serialization)->ObjectIdentifier(),
+ Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+
+ const internal::ProtoParametersSerialization* proto_serialization =
+ dynamic_cast<const internal::ProtoParametersSerialization*>(
+ serialization->get());
+ ASSERT_THAT(proto_serialization, NotNull());
+ EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(),
+ Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+ EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(),
+ Eq(test_case.output_prefix_type));
+
+ AesGcmKeyFormat key_format;
+ ASSERT_THAT(
+ key_format.ParseFromString(proto_serialization->GetKeyTemplate().value()),
+ IsTrue());
+ EXPECT_THAT(key_format.key_size(), Eq(test_case.key_size));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeParametersWithDisallowedIvSize) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(14)
+ .SetTagSizeInBytes(16)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeParameters<internal::ProtoParametersSerialization>(
+ *parameters);
+ EXPECT_THAT(serialization.status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeParametersWithDisallowedTagSize) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(12)
+ .SetTagSizeInBytes(14)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeParameters<internal::ProtoParametersSerialization>(
+ *parameters);
+ EXPECT_THAT(serialization.status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmProtoSerializationTest, ParseKey) {
+ TestCase test_case = GetParam();
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+ google::crypto::tink::AesGcmKey key_proto;
+ key_proto.set_version(0);
+ key_proto.set_key_value(raw_key_bytes);
+ RestrictedData serialized_key = RestrictedData(
+ key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+ util::StatusOr<internal::ProtoKeySerialization> serialization =
+ internal::ProtoKeySerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+ KeyData::SYMMETRIC, test_case.output_prefix_type, test_case.id);
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Key>> key =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+ *serialization, InsecureSecretKeyAccess::Get());
+ ASSERT_THAT(key, IsOk());
+ EXPECT_THAT((*key)->GetIdRequirement(), Eq(test_case.id));
+ EXPECT_THAT((*key)->GetParameters().HasIdRequirement(),
+ test_case.id.has_value());
+
+ util::StatusOr<AesGcmParameters> expected_parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(test_case.variant)
+ .SetKeySizeInBytes(test_case.key_size)
+ .SetIvSizeInBytes(test_case.iv_size)
+ .SetTagSizeInBytes(test_case.tag_size)
+ .Build();
+ ASSERT_THAT(expected_parameters, IsOk());
+
+ util::StatusOr<AesGcmKey> expected_key = AesGcmKey::Create(
+ *expected_parameters,
+ RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+ test_case.id, GetPartialKeyAccess());
+ ASSERT_THAT(expected_key, IsOk());
+
+ EXPECT_THAT(**key, Eq(*expected_key));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseLegacyKeyAsCrunchy) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(32);
+ google::crypto::tink::AesGcmKey key_proto;
+ key_proto.set_version(0);
+ key_proto.set_key_value(raw_key_bytes);
+ RestrictedData serialized_key = RestrictedData(
+ key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+ util::StatusOr<internal::ProtoKeySerialization> serialization =
+ internal::ProtoKeySerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+ KeyData::SYMMETRIC, OutputPrefixType::LEGACY, /*id_requirement=*/123);
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Key>> key =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+ *serialization, InsecureSecretKeyAccess::Get());
+ ASSERT_THAT(key, IsOk());
+
+ const AesGcmKey* aes_gcm_key = dynamic_cast<const AesGcmKey*>(key->get());
+ ASSERT_THAT(aes_gcm_key, NotNull());
+ EXPECT_THAT(aes_gcm_key->GetParameters().GetVariant(),
+ Eq(AesGcmParameters::Variant::kCrunchy));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseKeyWithInvalidSerialization) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ RestrictedData serialized_key =
+ RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get());
+
+ util::StatusOr<internal::ProtoKeySerialization> serialization =
+ internal::ProtoKeySerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+ KeyData::SYMMETRIC, OutputPrefixType::TINK,
+ /*id_requirement=*/0x23456789);
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Key>> key =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+ *serialization, InsecureSecretKeyAccess::Get());
+ EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseKeyNoSecretKeyAccess) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(16);
+ google::crypto::tink::AesGcmKey key_proto;
+ key_proto.set_version(0);
+ key_proto.set_key_value(raw_key_bytes);
+ RestrictedData serialized_key = RestrictedData(
+ key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+ util::StatusOr<internal::ProtoKeySerialization> serialization =
+ internal::ProtoKeySerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+ KeyData::SYMMETRIC, OutputPrefixType::TINK,
+ /*id_requirement=*/0x23456789);
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Key>> key =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+ *serialization, /*token=*/absl::nullopt);
+ EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseKeyWithInvalidVersion) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(16);
+ google::crypto::tink::AesGcmKey key_proto;
+ key_proto.set_version(1); // Invalid version number.
+ key_proto.set_key_value(raw_key_bytes);
+ RestrictedData serialized_key = RestrictedData(
+ key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+ util::StatusOr<internal::ProtoKeySerialization> serialization =
+ internal::ProtoKeySerialization::Create(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+ KeyData::SYMMETRIC, OutputPrefixType::TINK,
+ /*id_requirement=*/0x23456789);
+ ASSERT_THAT(serialization, IsOk());
+
+ util::StatusOr<std::unique_ptr<Key>> key =
+ internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+ *serialization, InsecureSecretKeyAccess::Get());
+ EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmProtoSerializationTest, SerializeKey) {
+ TestCase test_case = GetParam();
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(test_case.variant)
+ .SetKeySizeInBytes(test_case.key_size)
+ .SetIvSizeInBytes(test_case.iv_size)
+ .SetTagSizeInBytes(test_case.tag_size)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *parameters,
+ RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+ test_case.id, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeKey<internal::ProtoKeySerialization>(
+ *key, InsecureSecretKeyAccess::Get());
+ ASSERT_THAT(serialization, IsOk());
+ EXPECT_THAT((*serialization)->ObjectIdentifier(),
+ Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+
+ const internal::ProtoKeySerialization* proto_serialization =
+ dynamic_cast<const internal::ProtoKeySerialization*>(
+ serialization->get());
+ ASSERT_THAT(proto_serialization, NotNull());
+ EXPECT_THAT(proto_serialization->TypeUrl(),
+ Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+ EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+ EXPECT_THAT(proto_serialization->GetOutputPrefixType(),
+ Eq(test_case.output_prefix_type));
+ EXPECT_THAT(proto_serialization->IdRequirement(), Eq(test_case.id));
+
+ google::crypto::tink::AesGcmKey proto_key;
+ // OSS proto library complains if input is not converted to a string.
+ ASSERT_THAT(proto_key.ParseFromString(std::string(
+ proto_serialization->SerializedKeyProto().GetSecret(
+ InsecureSecretKeyAccess::Get()))),
+ IsTrue());
+ EXPECT_THAT(proto_key.key_value().size(), Eq(test_case.key_size));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeKeyWithDisallowedIvSize) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(14)
+ .SetTagSizeInBytes(16)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(32);
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *parameters,
+ RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+ /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeKey<internal::ProtoKeySerialization>(
+ *key, InsecureSecretKeyAccess::Get());
+ EXPECT_THAT(serialization.status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeKeyWithDisallowedTagSize) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .SetKeySizeInBytes(32)
+ .SetIvSizeInBytes(12)
+ .SetTagSizeInBytes(14)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(32);
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *parameters,
+ RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+ /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeKey<internal::ProtoKeySerialization>(
+ *key, InsecureSecretKeyAccess::Get());
+ EXPECT_THAT(serialization.status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeKeyNoSecretKeyAccess) {
+ ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+ util::StatusOr<AesGcmParameters> parameters =
+ AesGcmParameters::Builder()
+ .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+ .SetKeySizeInBytes(16)
+ .SetIvSizeInBytes(12)
+ .SetTagSizeInBytes(16)
+ .Build();
+ ASSERT_THAT(parameters, IsOk());
+
+ std::string raw_key_bytes = Random::GetRandomBytes(16);
+ util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+ *parameters,
+ RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+ /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+ ASSERT_THAT(key, IsOk());
+
+ util::StatusOr<std::unique_ptr<Serialization>> serialization =
+ internal::MutableSerializationRegistry::GlobalInstance()
+ .SerializeKey<internal::ProtoKeySerialization>(*key, absl::nullopt);
+ EXPECT_THAT(serialization.status(),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+} // namespace
+} // namespace tink
+} // namespace crypto
diff --git a/cc/aead/cord_aead.h b/cc/aead/cord_aead.h
index 46a60ce..e57ba5f 100644
--- a/cc/aead/cord_aead.h
+++ b/cc/aead/cord_aead.h
@@ -56,7 +56,7 @@
absl::Cord ciphertext,
absl::Cord associated_data) const = 0;
- virtual ~CordAead() {}
+ virtual ~CordAead() = default;
};
} // namespace tink
diff --git a/cc/aead/cord_aead_wrapper.cc b/cc/aead/cord_aead_wrapper.cc
index 0efb3a0..263b3fc 100644
--- a/cc/aead/cord_aead_wrapper.cc
+++ b/cc/aead/cord_aead_wrapper.cc
@@ -56,7 +56,7 @@
crypto::tink::util::StatusOr<absl::Cord> Decrypt(
absl::Cord ciphertext, absl::Cord associated_data) const override;
- ~CordAeadSetWrapper() override {}
+ ~CordAeadSetWrapper() override = default;
private:
std::unique_ptr<PrimitiveSet<CordAead>> aead_set_;
diff --git a/cc/aead/failing_aead.cc b/cc/aead/failing_aead.cc
index 81835c5..9f6c0f8 100644
--- a/cc/aead/failing_aead.cc
+++ b/cc/aead/failing_aead.cc
@@ -15,8 +15,10 @@
///////////////////////////////////////////////////////////////////////////////
#include "tink/aead/failing_aead.h"
+#include <memory>
#include <string>
#include <utility>
+
#include "absl/strings/string_view.h"
namespace crypto {
diff --git a/cc/aead/failing_aead.h b/cc/aead/failing_aead.h
index 70b80a9..88d8c81 100644
--- a/cc/aead/failing_aead.h
+++ b/cc/aead/failing_aead.h
@@ -16,6 +16,7 @@
#ifndef TINK_AEAD_FAILING_AEAD_H_
#define TINK_AEAD_FAILING_AEAD_H_
+#include <memory>
#include <string>
#include "absl/strings/string_view.h"
diff --git a/cc/aead/internal/BUILD.bazel b/cc/aead/internal/BUILD.bazel
index 0f7c72f..225bffc 100644
--- a/cc/aead/internal/BUILD.bazel
+++ b/cc/aead/internal/BUILD.bazel
@@ -11,6 +11,7 @@
"//util:errors",
"//util:statusor",
"@boringssl//:crypto",
+ "@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/status",
],
)
@@ -61,13 +62,11 @@
"//internal:ssl_unique_ptr",
"//subtle:random",
"//subtle:subtle_util",
- "//util:errors",
"//util:secret_data",
"//util:status",
"//util:statusor",
"@boringssl//:crypto",
"@com_google_absl//absl/status",
- "@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:cord",
],
)
@@ -154,7 +153,7 @@
name = "cord_aes_gcm_boringssl_test",
size = "small",
srcs = ["cord_aes_gcm_boringssl_test.cc"],
- data = ["@wycheproof//testvectors:aes_gcm"],
+ data = ["//testvectors:aes_gcm"],
deps = [
":cord_aes_gcm_boringssl",
"//subtle:aes_gcm_boringssl",
@@ -199,7 +198,7 @@
cc_test(
name = "zero_copy_aes_gcm_boringssl_test",
srcs = ["zero_copy_aes_gcm_boringssl_test.cc"],
- data = ["@wycheproof//testvectors:aes_gcm"],
+ data = ["//testvectors:aes_gcm"],
deps = [
":wycheproof_aead",
":zero_copy_aead",
@@ -235,21 +234,20 @@
name = "ssl_aead_test",
srcs = ["ssl_aead_test.cc"],
data = [
- "@wycheproof//testvectors:aes_gcm",
- "@wycheproof//testvectors:aes_gcm_siv",
- "@wycheproof//testvectors:chacha20_poly1305",
+ "//testvectors:aes_gcm",
+ "//testvectors:aes_gcm_siv",
+ "//testvectors:chacha20_poly1305",
],
deps = [
":ssl_aead",
":wycheproof_aead",
- "//config:tink_fips",
+ "//internal:fips_utils",
"//internal:ssl_util",
"//subtle:subtle_util",
"//util:secret_data",
"//util:statusor",
"//util:test_matchers",
"@com_google_absl//absl/container:flat_hash_set",
- "@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
@@ -265,6 +263,7 @@
":ssl_aead",
"//config:tink_fips",
"//internal:ssl_util",
+ "//internal:util",
"//subtle:subtle_util",
"//util:secret_data",
"//util:statusor",
diff --git a/cc/aead/internal/BUILD.gn b/cc/aead/internal/BUILD.gn
index ffabdb7..1202a08 100644
--- a/cc/aead/internal/BUILD.gn
+++ b/cc/aead/internal/BUILD.gn
@@ -14,6 +14,7 @@
"aead_util.h",
]
public_deps = [
+ "//third_party/abseil-cpp/absl/container:flat_hash_set",
"//third_party/abseil-cpp/absl/status:status",
"//third_party/boringssl:crypto",
"//third_party/tink/cc/util:errors",
@@ -60,13 +61,11 @@
":aead_util",
"//third_party/abseil-cpp/absl/status:status",
"//third_party/abseil-cpp/absl/strings:cord",
- "//third_party/abseil-cpp/absl/strings:strings",
"//third_party/boringssl:crypto",
"//third_party/tink/cc/aead:cord_aead",
"//third_party/tink/cc/internal:ssl_unique_ptr",
"//third_party/tink/cc/subtle:random",
"//third_party/tink/cc/subtle:subtle_util",
- "//third_party/tink/cc/util:errors",
"//third_party/tink/cc/util:secret_data",
"//third_party/tink/cc/util:status",
"//third_party/tink/cc/util:statusor",
diff --git a/cc/aead/internal/CMakeLists.txt b/cc/aead/internal/CMakeLists.txt
index e7c1a07..217d2b4 100644
--- a/cc/aead/internal/CMakeLists.txt
+++ b/cc/aead/internal/CMakeLists.txt
@@ -6,6 +6,7 @@
aead_util.cc
aead_util.h
DEPS
+ absl::flat_hash_set
absl::status
crypto
tink::util::errors
@@ -21,6 +22,7 @@
absl::strings
tink::subtle::wycheproof_util
tink::util::statusor
+ TESTONLY
)
tink_cc_library(
@@ -52,14 +54,12 @@
DEPS
tink::aead::internal::aead_util
absl::status
- absl::strings
absl::cord
crypto
tink::aead::cord_aead
tink::internal::ssl_unique_ptr
tink::subtle::random
tink::subtle::subtle_util
- tink::util::errors
tink::util::secret_data
tink::util::status
tink::util::statusor
@@ -74,6 +74,7 @@
gmock
absl::strings
tink::util::statusor
+ TESTONLY
)
tink_cc_library(
@@ -159,11 +160,10 @@
tink::aead::internal::wycheproof_aead
gmock
absl::flat_hash_set
- absl::memory
absl::status
absl::strings
absl::span
- tink::config::tink_fips
+ tink::internal::fips_utils
tink::internal::ssl_util
tink::subtle::subtle_util
tink::util::secret_data
@@ -241,6 +241,7 @@
absl::span
tink::config::tink_fips
tink::internal::ssl_util
+ tink::internal::util
tink::subtle::subtle_util
tink::util::secret_data
tink::util::statusor
diff --git a/cc/aead/internal/aead_from_zero_copy_test.cc b/cc/aead/internal/aead_from_zero_copy_test.cc
index 2fe4486..9693661 100644
--- a/cc/aead/internal/aead_from_zero_copy_test.cc
+++ b/cc/aead/internal/aead_from_zero_copy_test.cc
@@ -49,7 +49,7 @@
TEST(AeadFromZeroCopyTest, EncryptSucceeds) {
std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
- absl::WrapUnique(new MockZeroCopyAead());
+ std::make_unique<MockZeroCopyAead>();
EXPECT_CALL(*mock_zero_copy_aead, MaxEncryptionSize(kPlaintext.size()))
.WillOnce(Return(kCiphertext.size()));
EXPECT_CALL(*mock_zero_copy_aead, Encrypt(kPlaintext, kAssociatedData, _))
@@ -66,7 +66,7 @@
TEST(AeadFromZeroCopyTest, EncryptFailsIfZeroCopyEncryptFails) {
std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
- absl::WrapUnique(new MockZeroCopyAead());
+ std::make_unique<MockZeroCopyAead>();
EXPECT_CALL(*mock_zero_copy_aead, MaxEncryptionSize(kPlaintext.size()))
.WillOnce(Return(kCiphertext.size()));
EXPECT_CALL(*mock_zero_copy_aead, Encrypt(kPlaintext, kAssociatedData, _))
@@ -79,7 +79,7 @@
TEST(AeadFromZeroCopyTest, DecryptSucceeds) {
std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
- absl::WrapUnique(new MockZeroCopyAead());
+ std::make_unique<MockZeroCopyAead>();
EXPECT_CALL(*mock_zero_copy_aead, MaxDecryptionSize(kCiphertext.size()))
.WillOnce(Return(kPlaintext.size()));
EXPECT_CALL(*mock_zero_copy_aead, Decrypt(kCiphertext, kAssociatedData, _))
@@ -96,7 +96,7 @@
TEST(AeadFromZeroCopyTest, EncryptFailsIfZeroCopyDecryptFails) {
std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
- absl::WrapUnique(new MockZeroCopyAead());
+ std::make_unique<MockZeroCopyAead>();
EXPECT_CALL(*mock_zero_copy_aead, MaxDecryptionSize(kCiphertext.size()))
.WillOnce(Return(kPlaintext.size()));
EXPECT_CALL(*mock_zero_copy_aead, Decrypt(kCiphertext, kAssociatedData, _))
diff --git a/cc/aead/internal/aead_util.cc b/cc/aead/internal/aead_util.cc
index bfe0719..22c0bce 100644
--- a/cc/aead/internal/aead_util.cc
+++ b/cc/aead/internal/aead_util.cc
@@ -15,6 +15,8 @@
///////////////////////////////////////////////////////////////////////////////
#include "tink/aead/internal/aead_util.h"
+#include <string>
+
#include "absl/status/status.h"
#include "openssl/evp.h"
#include "tink/util/errors.h"
@@ -24,6 +26,18 @@
namespace tink {
namespace internal {
+bool IsSupportedKmsEnvelopeAeadDekKeyType(absl::string_view key_type) {
+ static const auto *kSupportedDekKeyTypes =
+ new absl::flat_hash_set<std::string>({
+ "type.googleapis.com/google.crypto.tink.AesGcmKey",
+ "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key",
+ "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey",
+ "type.googleapis.com/google.crypto.tink.AesEaxKey",
+ "type.googleapis.com/google.crypto.tink.AesGcmSivKey",
+ });
+ return kSupportedDekKeyTypes->contains(key_type);
+}
+
util::StatusOr<const EVP_CIPHER *> GetAesGcmCipherForKeySize(
uint32_t key_size_in_bytes) {
switch (key_size_in_bytes) {
diff --git a/cc/aead/internal/aead_util.h b/cc/aead/internal/aead_util.h
index c8c84d2..094b22d 100644
--- a/cc/aead/internal/aead_util.h
+++ b/cc/aead/internal/aead_util.h
@@ -16,6 +16,9 @@
#ifndef TINK_AEAD_INTERNAL_AEAD_UTIL_H_
#define TINK_AEAD_INTERNAL_AEAD_UTIL_H_
+#include <string>
+
+#include "absl/container/flat_hash_set.h"
#include "openssl/evp.h"
#include "tink/util/statusor.h"
@@ -23,6 +26,8 @@
namespace tink {
namespace internal {
+bool IsSupportedKmsEnvelopeAeadDekKeyType(absl::string_view key_type);
+
// Returns a pointer to an AES-GCM EVP_CIPHER for the given key size.
util::StatusOr<const EVP_CIPHER *> GetAesGcmCipherForKeySize(
uint32_t key_size_in_bytes);
diff --git a/cc/aead/internal/aead_util_test.cc b/cc/aead/internal/aead_util_test.cc
index ea3941a..83c717f 100644
--- a/cc/aead/internal/aead_util_test.cc
+++ b/cc/aead/internal/aead_util_test.cc
@@ -27,6 +27,8 @@
using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::IsOkAndHolds;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
using ::testing::Not;
TEST(AeadUtilTest, GetAesGcmCipherForKeySize) {
@@ -42,6 +44,15 @@
}
}
+TEST(AeadUtilTest, SupportedKmsEnvelopeAeadDekKeyTypes) {
+ EXPECT_THAT(IsSupportedKmsEnvelopeAeadDekKeyType(
+ "type.googleapis.com/google.crypto.tink.AesGcmKey"),
+ IsTrue());
+ EXPECT_THAT(IsSupportedKmsEnvelopeAeadDekKeyType(
+ "type.googleapis.com/google.crypto.tink.KmsEnvelopeAeadKey"),
+ IsFalse());
+}
+
#ifdef OPENSSL_IS_BORINGSSL
TEST(AeadUtilTest, GetAesAeadForKeySize) {
diff --git a/cc/aead/internal/cord_aes_gcm_boringssl.cc b/cc/aead/internal/cord_aes_gcm_boringssl.cc
index 321e9bf..4356bee 100644
--- a/cc/aead/internal/cord_aes_gcm_boringssl.cc
+++ b/cc/aead/internal/cord_aes_gcm_boringssl.cc
@@ -17,22 +17,18 @@
#include "tink/aead/internal/cord_aes_gcm_boringssl.h"
#include <cstdint>
-#include <iterator>
#include <memory>
#include <string>
#include <utility>
-#include <vector>
#include "absl/status/status.h"
#include "absl/strings/cord.h"
#include "openssl/evp.h"
-#include "openssl/err.h"
#include "tink/aead/cord_aead.h"
#include "tink/aead/internal/aead_util.h"
#include "tink/internal/ssl_unique_ptr.h"
#include "tink/subtle/random.h"
#include "tink/subtle/subtle_util.h"
-#include "tink/util/errors.h"
#include "tink/util/secret_data.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
@@ -47,8 +43,8 @@
// Set the IV `iv` for the given `context`. if `encryption` is true, set the
// context for encryption, and for decryption otherwise.
-util::Status SetIv(EVP_CIPHER_CTX* context, absl::string_view iv,
- bool encryption) {
+util::Status SetIvAndDirection(EVP_CIPHER_CTX* context, absl::string_view iv,
+ bool encryption) {
const int encryption_flag = encryption ? 1 : 0;
// Set the IV size.
if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, iv.size(),
@@ -67,28 +63,100 @@
return util::OkStatus();
}
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L
+// Returns a new EVP_CIPHER_CTX for encryption (`encryption` == true) or
+// decryption (`encryption` == false). It tries to skip part of the
+// initialization copying `partial_context`.
+util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> NewContextFromPartial(
+ EVP_CIPHER_CTX* partial_context, absl::string_view iv, bool encryption) {
+ internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
+ if (context == nullptr) {
+ return util::Status(absl::StatusCode::kInternal,
+ "EVP_CIPHER_CTX_new failed");
+ }
+ // Try making a copy of `partial_context` to skip some pre-computations.
+ //
+ // NOTE: With BoringSSL and OpenSSL 1.1.1 EVP_CIPHER_CTX_copy makes a copy
+ // of the `cipher_data` field of `context` as well, which contains the key
+ // material and IV (see [1] and [2]).
+ //
+ // [1]https://github.com/google/boringssl/blob/4c8bcf0da2951cacd8ed8eaa7fd2df4b22fca23b/crypto/fipsmodule/cipher/cipher.c#L116
+ // [2]https://github.com/openssl/openssl/blob/830bf8e1e4749ad65c51b6a1d0d769ae689404ba/crypto/evp/evp_enc.c#L703
+ if (EVP_CIPHER_CTX_copy(context.get(), partial_context) <= 0) {
+ return util::Status(absl::StatusCode::kInternal,
+ "EVP_CIPHER_CTX_copy failed");
+ }
+ util::Status res =
+ SetIvAndDirection(context.get(), iv, /*encryption=*/encryption);
+ if (!res.ok()) {
+ return res;
+ }
+ return std::move(context);
+}
+#else
+// Returns a new EVP_CIPHER_CTX for encryption (`encryption` == true) or
+// decryption (`encryption` == false) with given `key` and `iv`.
+//
+// NOTE: Copying the context fails with OpenSSL 3.0, which doesn't provide a
+// `dupctx` function for aead ciphers (see [1], [2]).
+//
+// [1]https://github.com/openssl/openssl/blob/eb52450f5151e8e78743ab05de21a344823316f5/crypto/evp/evp_enc.c#L1427
+// [2]https://github.com/openssl/openssl/blob/cac250755efd0c40cc6127a0e4baceb8d226c7e3/providers/implementations/include/prov/ciphercommon_aead.h#L30
+util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> NewContext(
+ const util::SecretData& key, absl::string_view iv, bool encryption) {
+ internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
+ if (context == nullptr) {
+ return util::Status(absl::StatusCode::kInternal,
+ "EVP_CIPHER_CTX_new failed");
+ }
+ util::StatusOr<const EVP_CIPHER*> cipher =
+ internal::GetAesGcmCipherForKeySize(key.size());
+ if (!cipher.ok()) {
+ return cipher.status();
+ }
+ if (EVP_CipherInit_ex(context.get(), *cipher, /*impl=*/nullptr,
+ reinterpret_cast<const uint8_t*>(key.data()),
+ /*iv=*/nullptr, /*enc=*/1) <= 0) {
+ return util::Status(absl::StatusCode::kInternal,
+ "Context initialization failed");
+ }
+ util::Status res =
+ SetIvAndDirection(context.get(), iv, /*encryption=*/encryption);
+ if (!res.ok()) {
+ return res;
+ }
+ return std::move(context);
+}
+#endif
+
} // namespace
-util::StatusOr<std::unique_ptr<CordAead> > CordAesGcmBoringSsl::New(
- util::SecretData key_value) {
+util::StatusOr<std::unique_ptr<CordAead>> CordAesGcmBoringSsl::New(
+ const util::SecretData& key_value) {
util::StatusOr<const EVP_CIPHER*> cipher =
internal::GetAesGcmCipherForKeySize(key_value.size());
if (!cipher.ok()) {
return cipher.status();
}
- internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
- // Initialize the cipher now to have some precomputations on the key. The
- // direction (enc/dec) is not important since it will be overwritten later.
- if (EVP_CipherInit_ex(context.get(), *cipher, /*engine=*/nullptr,
+ internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context(EVP_CIPHER_CTX_new());
+ // Initialize a partial context for the cipher to allow OpenSSL/BoringSSL
+ // making some precomputations on the key. Encrypt and Decrypt will try making
+ // a copy of this context to avoid doing the same initializations again and to
+ // guarantee thread safety.
+ //
+ // NOTE: It doesn't matter at this point if we set the direction to encryption
+ // or decryption, it will be overwritten later any time we call
+ // EVP_CipherInit_ex.
+ if (EVP_CipherInit_ex(partial_context.get(), *cipher, /*engine=*/nullptr,
reinterpret_cast<const uint8_t*>(&key_value[0]),
/*iv=*/nullptr, /*enc=*/1) <= 0) {
return util::Status(absl::StatusCode::kInternal,
"Context initialization failed");
}
- std::unique_ptr<CordAead> aead =
- absl::WrapUnique(new CordAesGcmBoringSsl(std::move(context)));
+ std::unique_ptr<CordAead> aead = absl::WrapUnique(
+ new CordAesGcmBoringSsl(std::move(partial_context), key_value));
return std::move(aead);
}
@@ -96,18 +164,21 @@
absl::Cord plaintext, absl::Cord associated_data) const {
std::string iv = subtle::Random::GetRandomBytes(kIvSizeInBytes);
- internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
- EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
- util::Status res = SetIv(context.get(), iv, /*encryption=*/true);
- if (!res.ok()) {
- return res;
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+ NewContextFromPartial(partial_context_.get(), iv, /*encryption=*/true);
+#else
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+ NewContext(key_, iv, /*encryption=*/true);
+#endif
+ if (!context.ok()) {
+ return context.status();
}
int len = 0;
// Process AAD.
for (auto ad_chunk : associated_data.Chunks()) {
- if (!EVP_EncryptUpdate(context.get(), /*out=*/nullptr, &len,
+ if (!EVP_EncryptUpdate(context->get(), /*out=*/nullptr, &len,
reinterpret_cast<const uint8_t*>(ad_chunk.data()),
ad_chunk.size())) {
return util::Status(absl::StatusCode::kInternal, "Encryption failed");
@@ -124,7 +195,7 @@
for (auto plaintext_chunk : plaintext.Chunks()) {
if (!EVP_EncryptUpdate(
- context.get(),
+ context->get(),
reinterpret_cast<uint8_t*>(&(buffer[ciphertext_buffer_offset])),
&len, reinterpret_cast<const uint8_t*>(plaintext_chunk.data()),
plaintext_chunk.size())) {
@@ -132,13 +203,14 @@
}
ciphertext_buffer_offset += plaintext_chunk.size();
}
- if (!EVP_EncryptFinal_ex(context.get(), nullptr, &len)) {
+ if (!EVP_EncryptFinal_ex(context->get(), nullptr, &len)) {
return util::Status(absl::StatusCode::kInternal, "Encryption failed");
}
std::string tag;
subtle::ResizeStringUninitialized(&tag, kTagSizeInBytes);
- if (!EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_GCM_GET_TAG, kTagSizeInBytes,
+ if (!EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_GCM_GET_TAG,
+ kTagSizeInBytes,
reinterpret_cast<uint8_t*>(&tag[0]))) {
return util::Status(absl::StatusCode::kInternal, "Encryption failed");
}
@@ -157,23 +229,26 @@
return util::Status(absl::StatusCode::kInternal, "Ciphertext too short");
}
- // First bytes contain IV.
+ // First bytes contain the IV.
std::string iv = std::string(ciphertext.Subcord(0, kIvSizeInBytes));
absl::Cord raw_ciphertext = ciphertext.Subcord(
kIvSizeInBytes, ciphertext.size() - kIvSizeInBytes - kTagSizeInBytes);
- internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
- EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
- util::Status res = SetIv(context.get(), iv, /*encryption=*/false);
- if (!res.ok()) {
- return res;
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+ NewContextFromPartial(partial_context_.get(), iv, /*encryption=*/false);
+#else
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+ NewContext(key_, iv, /*encryption=*/false);
+#endif
+ if (!context.ok()) {
+ return context.status();
}
int len = 0;
// Process associated data.
for (auto ad_chunk : associated_data.Chunks()) {
- if (!EVP_DecryptUpdate(context.get(), nullptr, &len,
+ if (!EVP_DecryptUpdate(context->get(), /*out=*/nullptr, &len,
reinterpret_cast<const uint8_t*>(ad_chunk.data()),
ad_chunk.size())) {
return util::Status(absl::StatusCode::kInternal, "Decryption failed");
@@ -192,7 +267,7 @@
});
for (auto ct_chunk : raw_ciphertext.Chunks()) {
- if (!EVP_DecryptUpdate(context.get(),
+ if (!EVP_DecryptUpdate(context->get(),
reinterpret_cast<uint8_t*>(
&plaintext_buffer[plaintext_buffer_offset]),
&len,
@@ -207,13 +282,13 @@
std::string tag = std::string(
ciphertext.Subcord(ciphertext.size() - kTagSizeInBytes, kTagSizeInBytes));
- if (!EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_GCM_SET_TAG, kTagSizeInBytes,
- &tag[0])) {
+ if (!EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_GCM_SET_TAG,
+ kTagSizeInBytes, &tag[0])) {
return util::Status(absl::StatusCode::kInternal,
"Could not set authentication tag");
}
// Verify authentication tag.
- if (!EVP_DecryptFinal_ex(context.get(), nullptr, &len)) {
+ if (!EVP_DecryptFinal_ex(context->get(), nullptr, &len)) {
return util::Status(absl::StatusCode::kInternal, "Authentication failed");
}
return result;
diff --git a/cc/aead/internal/cord_aes_gcm_boringssl.h b/cc/aead/internal/cord_aes_gcm_boringssl.h
index 9dcd288..273b523 100644
--- a/cc/aead/internal/cord_aes_gcm_boringssl.h
+++ b/cc/aead/internal/cord_aes_gcm_boringssl.h
@@ -20,12 +20,10 @@
#include <memory>
#include <utility>
-#include "absl/strings/string_view.h"
#include "openssl/evp.h"
#include "tink/aead/cord_aead.h"
#include "tink/internal/ssl_unique_ptr.h"
#include "tink/util/secret_data.h"
-#include "tink/util/status.h"
#include "tink/util/statusor.h"
namespace crypto {
@@ -35,7 +33,7 @@
class CordAesGcmBoringSsl : public CordAead {
public:
static crypto::tink::util::StatusOr<std::unique_ptr<CordAead>> New(
- util::SecretData key_value);
+ const util::SecretData& key_value);
crypto::tink::util::StatusOr<absl::Cord> Encrypt(
absl::Cord plaintext, absl::Cord associated_data) const override;
@@ -44,10 +42,15 @@
absl::Cord ciphertext, absl::Cord associated_data) const override;
private:
- explicit CordAesGcmBoringSsl(internal::SslUniquePtr<EVP_CIPHER_CTX> context)
- : context_(std::move(context)) {}
+ explicit CordAesGcmBoringSsl(
+ internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context,
+ const util::SecretData& key)
+ : partial_context_(std::move(partial_context)), key_(key) {}
- internal::SslUniquePtr<EVP_CIPHER_CTX> context_;
+ // Partially-initialized EVP_CIPHER_CTX context that is copied for every
+ // Encrypt/Decrypt operation.
+ internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context_;
+ util::SecretData key_;
};
} // namespace internal
diff --git a/cc/aead/internal/ssl_aead.cc b/cc/aead/internal/ssl_aead.cc
index 4b62a8a..c8b2629 100644
--- a/cc/aead/internal/ssl_aead.cc
+++ b/cc/aead/internal/ssl_aead.cc
@@ -44,36 +44,10 @@
ABSL_CONST_INIT const int kXchacha20Poly1305TagSizeInBytes = 16;
ABSL_CONST_INIT const int kAesGcmTagSizeInBytes = 16;
+ABSL_CONST_INIT const int kAesGcmSivTagSizeInBytes = 16;
namespace {
-// Sets `iv` to the given `context`, as well as the "direction"
-// (encrypt/decrypt) based on `encryption`.
-util::Status SetIvAndDirection(EVP_CIPHER_CTX *context, absl::string_view iv,
- bool encryption) {
- if (context == nullptr) {
- return util::Status(absl::StatusCode::kInternal,
- "Context must not be null");
- }
- const int encryption_flag = encryption ? 1 : 0;
- // Set the size for IV first, then set the IV bytes.
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_AEAD_SET_IVLEN, iv.size(),
- /*ptr=*/nullptr) <= 0) {
- return util::Status(absl::StatusCode::kInternal, "Setting IV size failed");
- }
- if (EVP_CipherInit_ex(context, /*cipher=*/nullptr, /*impl=*/nullptr,
- /*key=*/nullptr,
- reinterpret_cast<const uint8_t *>(iv.data()),
- /*enc=*/encryption_flag) <= 0) {
- return util::Status(
- absl::StatusCode::kInternal,
- absl::StrCat("Failed to initialize the context with IV of size ",
- iv.size(), " and for ",
- encryption ? "encryption" : "decryption"));
- }
- return util::OkStatus();
-}
-
// Encrypts/Decrypts `data` and writes the result into `out`. The direction
// (encrypt/decrypt) is given by `context`. `out` is assumed to be large enough
// to hold the encrypted/decrypted content.
@@ -108,9 +82,9 @@
class OpenSslOneShotAeadImpl : public SslOneShotAead {
public:
- OpenSslOneShotAeadImpl(internal::SslUniquePtr<EVP_CIPHER_CTX> context,
- size_t tag_size)
- : context_(std::move(context)), tag_size_(tag_size) {}
+ explicit OpenSslOneShotAeadImpl(const util::SecretData &key,
+ const EVP_CIPHER *cipher, size_t tag_size)
+ : key_(key), cipher_(cipher), tag_size_(tag_size) {}
util::StatusOr<int64_t> Encrypt(absl::string_view plaintext,
absl::string_view associated_data,
@@ -140,25 +114,15 @@
associated_data.size()));
}
- // For thread safety we copy the context and only then set the IV. This
- // allows to allocate an AesGcmBoringSsl cipher, and initialize the context
- // to force precomputation on the key, and only then set a different IV
- // for each call to `Encrypt`.
- internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
- // This makes a copy of the `cipher_data` field of the context too, which
- // contains the key material and IV (see
- // https://github.com/google/boringssl/blob/master/crypto/fipsmodule/cipher/cipher.c#L116).
- EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
- util::Status res =
- SetIvAndDirection(context.get(), iv, /*encryption=*/true);
- if (!res.ok()) {
- return res;
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+ GetContext(iv, /*encryption=*/true);
+ if (!context.ok()) {
+ return context.status();
}
// Set the associated data.
int len = 0;
- if (EVP_EncryptUpdate(context.get(), /*out=*/nullptr, &len,
+ if (EVP_EncryptUpdate(context->get(), /*out=*/nullptr, &len,
reinterpret_cast<const uint8_t *>(ad.data()),
ad.size()) <= 0) {
return util::Status(absl::StatusCode::kInternal,
@@ -166,18 +130,18 @@
}
util::StatusOr<int64_t> raw_ciphertext_bytes =
- UpdateCipher(context.get(), plaintext_data, out);
+ UpdateCipher(context->get(), plaintext_data, out);
if (!raw_ciphertext_bytes.ok()) {
return raw_ciphertext_bytes.status();
}
- if (EVP_EncryptFinal_ex(context.get(), /*out=*/nullptr, &len) <= 0) {
+ if (EVP_EncryptFinal_ex(context->get(), /*out=*/nullptr, &len) <= 0) {
return util::Status(absl::StatusCode::kInternal, "Finalization failed");
}
// Write the tag after the ciphertext.
absl::Span<char> tag = out.subspan(*raw_ciphertext_bytes, tag_size_);
- if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_GET_TAG, tag_size_,
+ if (EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_AEAD_GET_TAG, tag_size_,
reinterpret_cast<uint8_t *>(tag.data())) <= 0) {
return util::Status(absl::StatusCode::kInternal, "Failed to get the tag");
}
@@ -218,18 +182,15 @@
associated_data.size()));
}
- internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
- EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
- util::Status res =
- SetIvAndDirection(context.get(), iv, /*encryption=*/false);
- if (!res.ok()) {
- return res;
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+ GetContext(iv, /*encryption=*/false);
+ if (!context.ok()) {
+ return context.status();
}
int len = 0;
// Add the associated data.
- if (EVP_DecryptUpdate(context.get(), /*out=*/nullptr, &len,
+ if (EVP_DecryptUpdate(context->get(), /*out=*/nullptr, &len,
reinterpret_cast<const uint8_t *>(ad.data()),
ad.size()) <= 0) {
return util::Status(absl::StatusCode::kInternal,
@@ -246,7 +207,7 @@
auto tag = std::string(ciphertext.substr(raw_ciphertext_size, tag_size_));
// Set the tag.
- if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_SET_TAG, tag_size_,
+ if (EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_AEAD_SET_TAG, tag_size_,
reinterpret_cast<uint8_t *>(&tag[0])) <= 0) {
return util::Status(absl::StatusCode::kInternal,
"Could not set authentication tag");
@@ -267,12 +228,12 @@
absl::MakeCleanup([out] { OPENSSL_cleanse(out.data(), out.size()); });
util::StatusOr<int64_t> written_bytes =
- UpdateCipher(context.get(), raw_ciphertext, out_buffer);
+ UpdateCipher(context->get(), raw_ciphertext, out_buffer);
if (!written_bytes.ok()) {
return written_bytes.status();
}
- if (!EVP_DecryptFinal_ex(context.get(), /*out=*/nullptr, &len)) {
+ if (!EVP_DecryptFinal_ex(context->get(), /*out=*/nullptr, &len)) {
return util::Status(absl::StatusCode::kInternal, "Authentication failed");
}
@@ -281,20 +242,70 @@
return *written_bytes;
}
+ int64_t CiphertextSize(int64_t plaintext_length) const override {
+ return plaintext_length + tag_size_;
+ }
+
+ int64_t PlaintextSize(int64_t ciphertext_length) const override {
+ if (ciphertext_length < tag_size_) {
+ return 0;
+ }
+ return ciphertext_length - tag_size_;
+ }
+
private:
- const internal::SslUniquePtr<EVP_CIPHER_CTX> context_;
+ // Returns a new EVP_CIPHER_CTX for encryption (`ecryption` == true) or
+ // decryption (`encryption` == false).
+ util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> GetContext(
+ absl::string_view iv, bool encryption) const {
+ internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
+ if (context == nullptr) {
+ return util::Status(absl::StatusCode::kInternal,
+ "EVP_CIPHER_CTX_new failed");
+ }
+ const int encryption_flag = encryption ? 1 : 0;
+ if (EVP_CipherInit_ex(context.get(), cipher_, /*impl=*/nullptr,
+ /*key=*/nullptr, /*iv=*/nullptr,
+ encryption_flag) <= 0) {
+ return util::Status(
+ absl::StatusCode::kInternal,
+ absl::StrCat("Failed initializializing context for ",
+ encryption ? "encryption" : "decryption"));
+ }
+ // Set the size for IV first, then set the IV bytes.
+ if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_SET_IVLEN, iv.size(),
+ /*ptr=*/nullptr) <= 0) {
+ return util::Status(
+ absl::StatusCode::kInternal,
+ absl::StrCat("Failed stting size of the IV to ", iv.size()));
+ }
+ if (EVP_CipherInit_ex(context.get(), /*cipher=*/nullptr, /*impl=*/nullptr,
+ reinterpret_cast<const uint8_t *>(key_.data()),
+ reinterpret_cast<const uint8_t *>(iv.data()),
+ encryption_flag) <= 0) {
+ return util::Status(
+ absl::StatusCode::kInternal,
+ absl::StrCat("Failed to set key of size ", key_.size(),
+ "and IV of size ", iv.size()));
+ }
+
+ return std::move(context);
+ }
+
+ const util::SecretData key_;
+ const EVP_CIPHER *cipher_;
const size_t tag_size_;
};
#ifdef OPENSSL_IS_BORINGSSL
-// Implementation of the one-shot AEAD cypter. This is purposely internal to an
-// anonymous namespace to disallow direct use of this class other than through
-// the Create* functions below.
+// Implementation of the one-shot AEAD cypter. This is purposely internal to
+// an anonymous namespace to disallow direct use of this class other than
+// through the Create* functions below.
class BoringSslOneShotAeadImpl : public SslOneShotAead {
public:
- BoringSslOneShotAeadImpl(internal::SslUniquePtr<EVP_AEAD_CTX> context,
- size_t tag_size)
+ explicit BoringSslOneShotAeadImpl(
+ internal::SslUniquePtr<EVP_AEAD_CTX> context, size_t tag_size)
: context_(std::move(context)), tag_size_(tag_size) {}
util::StatusOr<int64_t> Encrypt(absl::string_view plaintext,
@@ -388,70 +399,24 @@
return out_len;
}
+ int64_t CiphertextSize(int64_t plaintext_length) const override {
+ return plaintext_length + tag_size_;
+ }
+
+ int64_t PlaintextSize(int64_t ciphertext_length) const override {
+ if (ciphertext_length < tag_size_) {
+ return 0;
+ }
+ return ciphertext_length - tag_size_;
+ }
+
+ private:
const internal::SslUniquePtr<EVP_AEAD_CTX> context_;
const size_t tag_size_;
};
#endif
-#ifdef OPENSSL_IS_BORINGSSL
-// Always use the BoringSSL APIs when available.
-using SslOneShotAeadImpl = BoringSslOneShotAeadImpl;
-#else
-using SslOneShotAeadImpl = OpenSslOneShotAeadImpl;
-#endif
-
-// One shot implementing AES-GCM.
-class SslAesGcmOneShotAead : public SslOneShotAeadImpl {
- public:
-#ifdef OPENSSL_IS_BORINGSSL
- explicit SslAesGcmOneShotAead(internal::SslUniquePtr<EVP_AEAD_CTX> context)
- : BoringSslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#else
- explicit SslAesGcmOneShotAead(internal::SslUniquePtr<EVP_CIPHER_CTX> context)
- : SslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#endif
-
- int64_t CiphertextSize(int64_t plaintext_length) const override {
- return plaintext_length + kAesGcmTagSizeInBytes;
- }
-
- int64_t PlaintextSize(int64_t ciphertext_length) const override {
- if (ciphertext_length < kAesGcmTagSizeInBytes) {
- return 0;
- }
- return ciphertext_length - kAesGcmTagSizeInBytes;
- }
-};
-
-// One shot implementing AES-GCM-SIV.
-using SslAesGcmSivOneShotAead = SslAesGcmOneShotAead;
-
-// One shot implementing XCHACHA-POLY-1305.
-class SslXchacha20Poly1305OneShotAead : public SslOneShotAeadImpl {
- public:
-#ifdef OPENSSL_IS_BORINGSSL
- explicit SslXchacha20Poly1305OneShotAead(
- internal::SslUniquePtr<EVP_AEAD_CTX> context)
- : BoringSslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#else
- explicit SslXchacha20Poly1305OneShotAead(
- internal::SslUniquePtr<EVP_CIPHER_CTX> context)
- : SslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#endif
-
- int64_t CiphertextSize(int64_t plaintext_length) const override {
- return plaintext_length + kXchacha20Poly1305TagSizeInBytes;
- }
-
- int64_t PlaintextSize(int64_t ciphertext_length) const override {
- if (ciphertext_length < kXchacha20Poly1305TagSizeInBytes) {
- return 0;
- }
- return ciphertext_length - kXchacha20Poly1305TagSizeInBytes;
- }
-};
-
} // namespace
util::StatusOr<std::unique_ptr<SslOneShotAead>> CreateAesGcmOneShotCrypter(
@@ -470,6 +435,8 @@
absl::StatusCode::kInternal,
absl::StrCat("EVP_AEAD_CTX_new failed: ", internal::GetSslErrors()));
}
+ return {absl::make_unique<BoringSslOneShotAeadImpl>(std::move(context),
+ kAesGcmTagSizeInBytes)};
#else
util::StatusOr<const EVP_CIPHER *> aead_cipher =
GetAesGcmCipherForKeySize(key.size());
@@ -477,25 +444,9 @@
return aead_cipher.status();
}
- internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-
- if (context == nullptr) {
- return util::Status(absl::StatusCode::kInternal,
- "EVP_CIPHER_CTX_new failed");
- }
-
- // Initialize the context for the cipher having OpenSSL to make some
- // precomputations on the key. It doesn't matter at this point if we set
- // encryption or decryption, it will be overwritten later on anyways any
- // time we call EVP_CipherInit_ex.
- if (EVP_CipherInit_ex(context.get(), *aead_cipher, /*impl=*/nullptr,
- reinterpret_cast<const uint8_t *>(&key[0]),
- /*iv=*/nullptr, /*enc=*/1) <= 0) {
- return util::Status(absl::StatusCode::kInternal,
- "Context initialization failed");
- }
+ return absl::make_unique<OpenSslOneShotAeadImpl>(key, *aead_cipher,
+ kAesGcmTagSizeInBytes);
#endif
- return {absl::make_unique<SslAesGcmOneShotAead>(std::move(context))};
}
util::StatusOr<std::unique_ptr<SslOneShotAead>> CreateAesGcmSivOneShotCrypter(
@@ -513,7 +464,8 @@
absl::StrCat("EVP_AEAD_CTX_new initialization Failed: ",
internal::GetSslErrors()));
}
- return {absl::make_unique<SslAesGcmSivOneShotAead>(std::move(context))};
+ return {absl::make_unique<BoringSslOneShotAeadImpl>(
+ std::move(context), kAesGcmSivTagSizeInBytes)};
#else
return util::Status(absl::StatusCode::kUnimplemented,
"AES-GCM-SIV is unimplemented for OpenSSL");
@@ -538,8 +490,8 @@
absl::StrCat("EVP_AEAD_CTX_new initialization Failed: ",
internal::GetSslErrors()));
}
- return {
- absl::make_unique<SslXchacha20Poly1305OneShotAead>(std::move(context))};
+ return {absl::make_unique<BoringSslOneShotAeadImpl>(
+ std::move(context), kXchacha20Poly1305TagSizeInBytes)};
#else
return util::Status(absl::StatusCode::kUnimplemented,
"Xchacha20-Poly1305 is unimplemented for OpenSSL");
diff --git a/cc/aead/internal/ssl_aead.h b/cc/aead/internal/ssl_aead.h
index 6ff877a..3fdc158 100644
--- a/cc/aead/internal/ssl_aead.h
+++ b/cc/aead/internal/ssl_aead.h
@@ -28,9 +28,10 @@
namespace tink {
namespace internal {
+// Tag sizes.
ABSL_CONST_INIT extern const int kXchacha20Poly1305TagSizeInBytes;
-// Tag size for both AES-GCM and AES-GCM-SIV.
ABSL_CONST_INIT extern const int kAesGcmTagSizeInBytes;
+ABSL_CONST_INIT extern const int kAesGcmSivTagSizeInBytes;
// Interface for one-shot AEAD crypters.
class SslOneShotAead {
diff --git a/cc/aead/internal/ssl_aead_large_inputs_test.cc b/cc/aead/internal/ssl_aead_large_inputs_test.cc
index 1ca2400..9427b78 100644
--- a/cc/aead/internal/ssl_aead_large_inputs_test.cc
+++ b/cc/aead/internal/ssl_aead_large_inputs_test.cc
@@ -33,6 +33,7 @@
#include "tink/aead/internal/ssl_aead.h"
#include "tink/config/tink_fips.h"
#include "tink/internal/ssl_util.h"
+#include "tink/internal/util.h"
#include "tink/subtle/subtle_util.h"
#include "tink/util/secret_data.h"
#include "tink/util/statusor.h"
@@ -87,6 +88,9 @@
// Encrypt/decrypt with an input larger than a MAX int.
TEST_P(SslOneShotAeadLargeInputsTest, EncryptDecryptLargeInput) {
+ if (IsWindows()) {
+ GTEST_SKIP() << "Skipping on Windows: this currently times out";
+ }
const int64_t buff_size =
static_cast<int64_t>(std::numeric_limits<int>::max()) + 1024;
std::string large_input(buff_size, '0');
diff --git a/cc/aead/internal/ssl_aead_test.cc b/cc/aead/internal/ssl_aead_test.cc
index 375e9cd..23fd35b 100644
--- a/cc/aead/internal/ssl_aead_test.cc
+++ b/cc/aead/internal/ssl_aead_test.cc
@@ -34,7 +34,7 @@
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "tink/aead/internal/wycheproof_aead.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
#include "tink/internal/ssl_util.h"
#include "tink/subtle/subtle_util.h"
#include "tink/util/secret_data.h"
@@ -524,7 +524,7 @@
}
TEST(SslOneShotAeadTest, AesGcmTestFipsOnly) {
- if (IsFipsModeEnabled() && !FIPS_mode()) {
+ if (IsFipsModeEnabled() && !IsFipsEnabledInSsl()) {
GTEST_SKIP() << "Test should not run in FIPS mode when BoringCrypto is "
"unavailable.";
}
@@ -539,7 +539,7 @@
}
TEST(SslOneShotAeadTest, AesGcmTestTestFipsFailWithoutBoringCrypto) {
- if (!IsFipsModeEnabled() || FIPS_mode()) {
+ if (!IsFipsModeEnabled() || IsFipsEnabledInSsl()) {
GTEST_SKIP()
<< "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
}
diff --git a/cc/aead/internal/zero_copy_aead_wrapper.cc b/cc/aead/internal/zero_copy_aead_wrapper.cc
index 3ed95cc..b1e8114 100644
--- a/cc/aead/internal/zero_copy_aead_wrapper.cc
+++ b/cc/aead/internal/zero_copy_aead_wrapper.cc
@@ -60,7 +60,7 @@
absl::string_view ciphertext,
absl::string_view associated_data) const override;
- ~ZeroCopyAeadSetWrapper() override {}
+ ~ZeroCopyAeadSetWrapper() override = default;
private:
std::unique_ptr<PrimitiveSet<ZeroCopyAead>> aead_set_;
diff --git a/cc/aead/internal/zero_copy_aead_wrapper.h b/cc/aead/internal/zero_copy_aead_wrapper.h
index 1964a6d..beca0aa 100644
--- a/cc/aead/internal/zero_copy_aead_wrapper.h
+++ b/cc/aead/internal/zero_copy_aead_wrapper.h
@@ -17,6 +17,8 @@
#ifndef TINK_AEAD_INTERNAL_ZERO_COPY_AEAD_WRAPPER_H_
#define TINK_AEAD_INTERNAL_ZERO_COPY_AEAD_WRAPPER_H_
+#include <memory>
+
#include "tink/aead.h"
#include "tink/aead/internal/zero_copy_aead.h"
#include "tink/primitive_set.h"
@@ -46,4 +48,4 @@
} // namespace tink
} // namespace crypto
-#endif // TINK_AEAD_AEAD_WRAPPER_H_
+#endif // TINK_AEAD_INTERNAL_ZERO_COPY_AEAD_WRAPPER_H_
diff --git a/cc/aead/internal/zero_copy_aead_wrapper_test.cc b/cc/aead/internal/zero_copy_aead_wrapper_test.cc
index 489b7de..b4e2218 100644
--- a/cc/aead/internal/zero_copy_aead_wrapper_test.cc
+++ b/cc/aead/internal/zero_copy_aead_wrapper_test.cc
@@ -16,6 +16,7 @@
#include "tink/aead/internal/zero_copy_aead_wrapper.h"
+#include <cstring>
#include <memory>
#include <string>
#include <utility>
diff --git a/cc/aead/kms_envelope_aead.cc b/cc/aead/kms_envelope_aead.cc
index 887c253..8f36b0d 100644
--- a/cc/aead/kms_envelope_aead.cc
+++ b/cc/aead/kms_envelope_aead.cc
@@ -27,6 +27,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tink/aead.h"
+#include "tink/aead/internal/aead_util.h"
#include "tink/registry.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
@@ -60,6 +61,11 @@
util::StatusOr<std::unique_ptr<Aead>> KmsEnvelopeAead::New(
const google::crypto::tink::KeyTemplate& dek_template,
std::unique_ptr<Aead> remote_aead) {
+ if (!internal::IsSupportedKmsEnvelopeAeadDekKeyType(
+ dek_template.type_url())) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "unsupported key type");
+ }
if (remote_aead == nullptr) {
return util::Status(absl::StatusCode::kInvalidArgument,
"remote_aead must be non-null");
diff --git a/cc/aead/kms_envelope_aead.h b/cc/aead/kms_envelope_aead.h
index 1fd9d89..acfe066 100644
--- a/cc/aead/kms_envelope_aead.h
+++ b/cc/aead/kms_envelope_aead.h
@@ -58,7 +58,7 @@
absl::string_view ciphertext,
absl::string_view associated_data) const override;
- ~KmsEnvelopeAead() override {}
+ ~KmsEnvelopeAead() override = default;
private:
KmsEnvelopeAead(const google::crypto::tink::KeyTemplate& dek_template,
diff --git a/cc/aead/kms_envelope_aead_key_manager.h b/cc/aead/kms_envelope_aead_key_manager.h
index 04b9e31..69c99d1 100644
--- a/cc/aead/kms_envelope_aead_key_manager.h
+++ b/cc/aead/kms_envelope_aead_key_manager.h
@@ -25,6 +25,7 @@
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "tink/aead.h"
+#include "tink/aead/internal/aead_util.h"
#include "tink/core/key_type_manager.h"
#include "tink/core/template_util.h"
#include "tink/internal/fips_utils.h"
@@ -75,6 +76,11 @@
return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
"Missing kek_uri.");
}
+ if (!internal::IsSupportedKmsEnvelopeAeadDekKeyType(
+ format.dek_template().type_url())) {
+ return util::Status(absl::StatusCode::kInvalidArgument,
+ "unsupported dek key type");
+ }
return util::OkStatus();
}
diff --git a/cc/aead/kms_envelope_aead_key_manager_test.cc b/cc/aead/kms_envelope_aead_key_manager_test.cc
index 7b32aa1..4cee9c1 100644
--- a/cc/aead/kms_envelope_aead_key_manager_test.cc
+++ b/cc/aead/kms_envelope_aead_key_manager_test.cc
@@ -26,13 +26,16 @@
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
#include "tink/aead/aead_key_templates.h"
#include "tink/aead/aes_eax_key_manager.h"
#include "tink/aead/kms_envelope_aead.h"
#include "tink/kms_client.h"
#include "tink/kms_clients.h"
+#include "tink/mac/mac_key_templates.h"
#include "tink/registry.h"
#include "tink/subtle/aead_test_util.h"
+#include "tink/util/fake_kms_client.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "tink/util/test_matchers.h"
@@ -46,8 +49,9 @@
using ::crypto::tink::test::DummyAead;
using ::crypto::tink::test::DummyKmsClient;
using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
using ::crypto::tink::test::StatusIs;
-using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::KeyTemplate;
using ::google::crypto::tink::KmsEnvelopeAeadKey;
using ::google::crypto::tink::KmsEnvelopeAeadKeyFormat;
using ::testing::Eq;
@@ -118,7 +122,15 @@
TEST(KmsEnvelopeAeadKeyManagerTest, ValidateKeyFormatNoTemplate) {
KmsEnvelopeAeadKeyFormat key_format;
- *key_format.mutable_dek_template() = AeadKeyTemplates::Aes128Eax();
+ key_format.set_kek_uri("Some uri");
+ EXPECT_THAT(KmsEnvelopeAeadKeyManager().ValidateKeyFormat(key_format),
+ Not(IsOk()));
+}
+
+TEST(KmsEnvelopeAeadKeyManagerTest, ValidateKeyFormatInvalidDekTemplate) {
+ KmsEnvelopeAeadKeyFormat key_format;
+ key_format.set_kek_uri("Some uri");
+ *key_format.mutable_dek_template() = MacKeyTemplates::HmacSha256();
EXPECT_THAT(KmsEnvelopeAeadKeyManager().ValidateKeyFormat(key_format),
Not(IsOk()));
}
@@ -229,6 +241,54 @@
IsOk());
}
+class KmsEnvelopeAeadKeyManagerDekTemplatesTest
+ : public testing::TestWithParam<KeyTemplate> {
+ void SetUp() override { ASSERT_THAT(AeadConfig::Register(), IsOk()); }
+};
+
+TEST_P(KmsEnvelopeAeadKeyManagerDekTemplatesTest, EncryptDecryp) {
+ util::StatusOr<std::string> kek_uri_result =
+ test::FakeKmsClient::CreateFakeKeyUri();
+ ASSERT_THAT(kek_uri_result, IsOk());
+ std::string kek_uri = kek_uri_result.value();
+ util::Status register_fake_kms_client_status =
+ test::FakeKmsClient::RegisterNewClient(kek_uri, /*credentials_path=*/"");
+ ASSERT_THAT(register_fake_kms_client_status, IsOk());
+
+ KeyTemplate dek_template = GetParam();
+ KeyTemplate env_template =
+ AeadKeyTemplates::KmsEnvelopeAead(kek_uri, dek_template);
+ util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+ KeysetHandle::GenerateNew(env_template);
+ ASSERT_THAT(handle, IsOk());
+ util::StatusOr<std::unique_ptr<Aead>> envelope_aead =
+ (*handle)->GetPrimitive<Aead>();
+ ASSERT_THAT(envelope_aead, IsOk());
+
+ std::string plaintext = "plaintext";
+ std::string associated_data = "associated_data";
+ util::StatusOr<std::string> ciphertext =
+ (*envelope_aead)->Encrypt(plaintext, associated_data);
+ ASSERT_THAT(ciphertext, IsOk());
+ util::StatusOr<std::string> decrypted =
+ (*envelope_aead)->Decrypt(ciphertext.value(), associated_data);
+ EXPECT_THAT(decrypted, IsOkAndHolds(plaintext));
+
+ std::string invalid_associated_data = "invalid_associated_data";
+ util::StatusOr<std::string> decrypted_with_invalid_associated_data =
+ (*envelope_aead)->Decrypt(ciphertext.value(), invalid_associated_data);
+ EXPECT_THAT(decrypted_with_invalid_associated_data.status(), Not(IsOk()));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ KmsEnvelopeAeadKeyManagerDekTemplatesTest,
+ KmsEnvelopeAeadKeyManagerDekTemplatesTest,
+ testing::Values(AeadKeyTemplates::Aes128Gcm(),
+ AeadKeyTemplates::Aes256Gcm(),
+ AeadKeyTemplates::Aes128CtrHmacSha256(),
+ AeadKeyTemplates::Aes128Eax(),
+ AeadKeyTemplates::Aes128GcmNoPrefix()));
+
} // namespace
} // namespace tink
} // namespace crypto
diff --git a/cc/aead/kms_envelope_aead_test.cc b/cc/aead/kms_envelope_aead_test.cc
index 673a1b5..ef8395d 100644
--- a/cc/aead/kms_envelope_aead_test.cc
+++ b/cc/aead/kms_envelope_aead_test.cc
@@ -21,15 +21,18 @@
#include <memory>
#include <string>
#include <utility>
+#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/internal/endian.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
#include "tink/aead.h"
#include "tink/aead/aead_config.h"
#include "tink/aead/aead_key_templates.h"
+#include "tink/keyset_handle.h"
#include "tink/mac/mac_key_templates.h"
#include "tink/registry.h"
#include "tink/util/status.h"
@@ -37,147 +40,249 @@
#include "tink/util/test_matchers.h"
#include "tink/util/test_util.h"
#include "proto/aes_gcm.pb.h"
+#include "tink/internal/ssl_util.h"
namespace crypto {
namespace tink {
namespace {
-using crypto::tink::test::DummyAead;
-using crypto::tink::test::IsOk;
-using crypto::tink::test::StatusIs;
-using testing::HasSubstr;
+using ::crypto::tink::Aead;
+using ::crypto::tink::test::DummyAead;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyTemplate;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::SizeIs;
+using ::testing::Test;
+constexpr int kEncryptedDekPrefixSize = 4;
+constexpr absl::string_view kRemoteAeadName = "kms-backed-aead";
-TEST(KmsEnvelopeAeadTest, BasicEncryptDecrypt) {
- EXPECT_THAT(AeadConfig::Register(), IsOk());
+class KmsEnvelopeAeadTest : public Test {
+ protected:
+ void TearDown() override { Registry::Reset(); }
+};
- auto dek_template = AeadKeyTemplates::Aes128Eax();
- std::string remote_aead_name = "kms-backed-aead";
- auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
+TEST_F(KmsEnvelopeAeadTest, EncryptDecryptSucceed) {
+ ASSERT_THAT(AeadConfig::Register(), IsOk());
- auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
- EXPECT_THAT(aead_result, IsOk());
- auto aead = std::move(aead_result.value());
+ // Use an AES-128-GCM primitive as the remote one.
+ util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+ KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+ ASSERT_THAT(keyset_handle, IsOk());
+ KeyTemplate dek_template = AeadKeyTemplates::Aes128Eax();
+ util::StatusOr<std::unique_ptr<Aead>> remote_aead =
+ (*keyset_handle)->GetPrimitive<Aead>();
+
+ util::StatusOr<std::unique_ptr<Aead>> envelope_aead =
+ KmsEnvelopeAead::New(dek_template, *std::move(remote_aead));
+ ASSERT_THAT(envelope_aead, IsOk());
+
std::string message = "Some data to encrypt.";
- std::string aad = "Some data to authenticate.";
- auto encrypt_result = aead->Encrypt(message, aad);
- EXPECT_THAT(encrypt_result, IsOk());
- auto decrypt_result = aead->Decrypt(encrypt_result.value(), aad);
- EXPECT_THAT(decrypt_result, IsOk());
- EXPECT_EQ(decrypt_result.value(), message);
+ std::string aad = "Some associated data.";
+ util::StatusOr<std::string> encrypt_result =
+ (*envelope_aead)->Encrypt(message, aad);
+ ASSERT_THAT(encrypt_result, IsOk());
+ util::StatusOr<std::string> decrypt_result =
+ (*envelope_aead)->Decrypt(encrypt_result.value(), aad);
+ EXPECT_THAT(decrypt_result, IsOkAndHolds(message));
}
-TEST(KmsEnvelopeAeadTest, NullAead) {
- auto dek_template = AeadKeyTemplates::Aes128Eax();
- auto aead_result = KmsEnvelopeAead::New(dek_template, nullptr);
- EXPECT_THAT(aead_result.status(), StatusIs(absl::StatusCode::kInvalidArgument,
- HasSubstr("non-null")));
-}
-
-TEST(KmsEnvelopeAeadTest, MissingDekKeyManager) {
- Registry::Reset();
- auto dek_template = AeadKeyTemplates::Aes128Eax();
- std::string remote_aead_name = "kms-backed-aead";
- auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
- auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
- EXPECT_THAT(aead_result.status(),
- StatusIs(absl::StatusCode::kNotFound, HasSubstr("AesEaxKey")));
-}
-
-TEST(KmsEnvelopeAeadTest, WrongDekPrimitive) {
- EXPECT_THAT(AeadConfig::Register(), IsOk());
- auto dek_template = MacKeyTemplates::HmacSha256();
- std::string remote_aead_name = "kms-backed-aead";
- auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
- auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
- EXPECT_THAT(aead_result.status(),
- StatusIs(absl::StatusCode::kInvalidArgument,
- HasSubstr("not among supported primitives")));
-}
-
-TEST(KmsEnvelopeAeadTest, DecryptionErrors) {
- EXPECT_THAT(AeadConfig::Register(), IsOk());
-
- auto dek_template = AeadKeyTemplates::Aes128Gcm();
- std::string remote_aead_name = "kms-backed-aead";
- auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-
- auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
- EXPECT_THAT(aead_result, IsOk());
- auto aead = std::move(aead_result.value());
- std::string message = "Some data to encrypt.";
- std::string aad = "Some data to authenticate.";
- auto encrypt_result = aead->Encrypt(message, aad);
- EXPECT_THAT(encrypt_result, IsOk());
- auto ct = encrypt_result.value();
-
- // Empty ciphertext.
- auto decrypt_result = aead->Decrypt("", aad);
+TEST_F(KmsEnvelopeAeadTest, NewFailsIfReamoteAeadIsNull) {
+ KeyTemplate dek_template = AeadKeyTemplates::Aes128Eax();
EXPECT_THAT(
- decrypt_result.status(),
+ KmsEnvelopeAead::New(dek_template, /*remote_aead=*/nullptr).status(),
+ StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("non-null")));
+}
+
+TEST_F(KmsEnvelopeAeadTest, NewFailsIfDekKeyManagerIsNotRegistered) {
+ KeyTemplate dek_template = AeadKeyTemplates::Aes128Eax();
+ auto remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+ EXPECT_THAT(
+ KmsEnvelopeAead::New(dek_template, std::move(remote_aead)).status(),
+ StatusIs(absl::StatusCode::kNotFound, HasSubstr("AesEaxKey")));
+}
+
+TEST_F(KmsEnvelopeAeadTest, NewFailsIfUsingDekTemplateOfUnsupportedKeyType) {
+ ASSERT_THAT(AeadConfig::Register(), IsOk());
+ KeyTemplate dek_template = MacKeyTemplates::HmacSha256();
+ auto remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+ EXPECT_THAT(
+ KmsEnvelopeAead::New(dek_template, std::move(remote_aead)).status(),
+ StatusIs(absl::StatusCode::kInvalidArgument,
+ HasSubstr("unsupported key type")));
+}
+
+TEST_F(KmsEnvelopeAeadTest, DecryptFailsWithInvalidCiphertextOrAad) {
+ ASSERT_THAT(AeadConfig::Register(), IsOk());
+
+ KeyTemplate dek_template = AeadKeyTemplates::Aes128Gcm();
+ auto remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+ util::StatusOr<std::unique_ptr<Aead>> aead =
+ KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
+ ASSERT_THAT(aead, IsOk());
+
+ std::string message = "Some data to encrypt.";
+ std::string aad = "Some associated data.";
+ util::StatusOr<std::string> encrypt_result = (*aead)->Encrypt(message, aad);
+ ASSERT_THAT(encrypt_result, IsOk());
+ auto ciphertext = absl::string_view(*encrypt_result);
+
+ // Ciphertext has size zero or smaller than 4 bytes.
+ EXPECT_THAT(
+ (*aead)->Decrypt(/*ciphertext=*/"", aad).status(),
+ StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("too short")));
+ EXPECT_THAT(
+ (*aead)->Decrypt(/*ciphertext=*/"sh", aad).status(),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("too short")));
- // Short ciphertext.
- decrypt_result = aead->Decrypt("sh", aad);
+ // Ciphertext is smaller than the size of the key.
+ const int dek_encrypted_key_size = absl::big_endian::Load32(
+ reinterpret_cast<const uint8_t*>(ciphertext.data()));
+ // We leave only key size and key truncated by one.
EXPECT_THAT(
- decrypt_result.status(),
- StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("too short")));
-
- // Truncated ciphertext.
- decrypt_result = aead->Decrypt(ct.substr(2), aad);
- EXPECT_THAT(
- decrypt_result.status(),
+ (*aead)
+ ->Decrypt(ciphertext.substr(0, 4 + dek_encrypted_key_size - 1), aad)
+ .status(),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("invalid")));
- // Corrupted ciphertext.
- auto ct_copy = ct;
- ct_copy[4] = 'a'; // corrupt serialized DEK.
- decrypt_result = aead->Decrypt(ct_copy, aad);
+ std::string corrupted_ciphertext = *encrypt_result;
+ // Corrupt the serialized DEK.
+ corrupted_ciphertext[4] = 'a';
EXPECT_THAT(
- decrypt_result.status(),
+ (*aead)->Decrypt(corrupted_ciphertext, aad).status(),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("invalid")));
// Wrong associated data.
- decrypt_result = aead->Decrypt(ct, "wrong aad");
- EXPECT_THAT(decrypt_result.status(),
+ EXPECT_THAT((*aead)->Decrypt(ciphertext, "wrong aad").status(),
StatusIs(absl::StatusCode::kInternal,
HasSubstr("Authentication failed")));
}
-TEST(KmsEnvelopeAeadTest, KeyFormat) {
- EXPECT_THAT(AeadConfig::Register(), IsOk());
+TEST_F(KmsEnvelopeAeadTest, DekMaintainsCorrectKeyFormat) {
+ ASSERT_THAT(AeadConfig::Register(), IsOk());
- auto dek_template = AeadKeyTemplates::Aes128Gcm();
+ KeyTemplate dek_template = AeadKeyTemplates::Aes128Gcm();
+ auto kms_remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+ util::StatusOr<std::unique_ptr<Aead>> aead =
+ KmsEnvelopeAead::New(dek_template, std::move(kms_remote_aead));
+ ASSERT_THAT(aead, IsOk());
- // Construct a remote AEAD which uses same key template for this test.
- std::string remote_aead_name = "kms-backed-aead";
- auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-
- // Create envelope AEAD and encrypt some data.
- auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
- EXPECT_THAT(aead_result, IsOk());
-
- auto aead = std::move(aead_result.value());
std::string message = "Some data to encrypt.";
- std::string aad = "Some data to authenticate.";
- auto encrypt_result = aead->Encrypt(message, aad);
- EXPECT_THAT(encrypt_result, IsOk());
- auto ct = encrypt_result.value();
+ std::string aad = "Some associated data.";
+ util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(message, aad);
+ ASSERT_THAT(ciphertext, IsOk());
- // Recover DEK from ciphertext
- auto enc_dek_size =
- absl::big_endian::Load32(reinterpret_cast<const uint8_t*>(ct.data()));
-
- remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
- auto dek_decrypt_result =
- remote_aead->Decrypt(ct.substr(4, enc_dek_size), "");
+ // Recover DEK from ciphertext (see
+ // https://developers.google.com/tink/wire-format#envelope_encryption).
+ auto enc_dek_size = absl::big_endian::Load32(
+ reinterpret_cast<const uint8_t*>(ciphertext->data()));
+ DummyAead remote_aead = DummyAead(kRemoteAeadName);
+ absl::string_view encrypted_dek =
+ absl::string_view(*ciphertext)
+ .substr(kEncryptedDekPrefixSize, enc_dek_size);
+ util::StatusOr<std::string> dek_proto_bytes =
+ remote_aead.Decrypt(encrypted_dek,
+ /*associated_data=*/"");
+ ASSERT_THAT(dek_proto_bytes, IsOk());
// Check if we can deserialize a GCM key proto from the decrypted DEK.
google::crypto::tink::AesGcmKey key;
- EXPECT_THAT(key.ParseFromString(dek_decrypt_result.value()), true);
- EXPECT_THAT(key.key_value().size(), testing::Eq(16));
+ EXPECT_TRUE(key.ParseFromString(dek_proto_bytes.value()));
+ EXPECT_THAT(key.key_value(), SizeIs(16));
}
+TEST_F(KmsEnvelopeAeadTest, MultipleEncryptionsProduceDifferentDeks) {
+ ASSERT_THAT(AeadConfig::Register(), IsOk());
+
+ KeyTemplate dek_template = AeadKeyTemplates::Aes128Gcm();
+ auto kms_remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+ util::StatusOr<std::unique_ptr<Aead>> aead =
+ KmsEnvelopeAead::New(dek_template, std::move(kms_remote_aead));
+ ASSERT_THAT(aead, IsOk());
+
+ std::string message = "Some data to encrypt.";
+ std::string aad = "Some associated data.";
+
+ constexpr int kNumIterations = 2;
+ std::vector<google::crypto::tink::AesGcmKey> ciphertexts;
+ ciphertexts.reserve(kNumIterations);
+ for (int i = 0; i < kNumIterations; i++) {
+ util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(message, aad);
+ ASSERT_THAT(ciphertext, IsOk());
+
+ auto enc_dek_size = absl::big_endian::Load32(
+ reinterpret_cast<const uint8_t*>(ciphertext->data()));
+ DummyAead remote_aead = DummyAead(kRemoteAeadName);
+ util::StatusOr<std::string> dek_proto_bytes = remote_aead.Decrypt(
+ ciphertext->substr(kEncryptedDekPrefixSize, enc_dek_size),
+ /*associated_data=*/"");
+ ASSERT_THAT(dek_proto_bytes, IsOk());
+
+ google::crypto::tink::AesGcmKey key;
+ ASSERT_TRUE(key.ParseFromString(dek_proto_bytes.value()));
+ ASSERT_THAT(key.key_value(), SizeIs(16));
+ ciphertexts.push_back(key);
+ }
+
+ for (int i = 0; i < ciphertexts.size() - 1; i++) {
+ for (int j = i + 1; j < ciphertexts.size(); j++) {
+ EXPECT_THAT(ciphertexts[i].SerializeAsString(),
+ Not(Eq(ciphertexts[j].SerializeAsString())));
+ }
+ }
+}
+
+class KmsEnvelopeAeadDekTemplatesTest
+ : public testing::TestWithParam<KeyTemplate> {
+ void SetUp() override { ASSERT_THAT(AeadConfig::Register(), IsOk()); }
+};
+
+TEST_P(KmsEnvelopeAeadDekTemplatesTest, EncryptDecrypt) {
+ // Use an AES-128-GCM primitive as the remote AEAD.
+ util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+ KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+ ASSERT_THAT(keyset_handle, IsOk());
+ util::StatusOr<std::unique_ptr<Aead>> remote_aead =
+ (*keyset_handle)->GetPrimitive<Aead>();
+
+ KeyTemplate dek_template = GetParam();
+ util::StatusOr<std::unique_ptr<Aead>> envelope_aead =
+ KmsEnvelopeAead::New(dek_template, *std::move(remote_aead));
+ ASSERT_THAT(envelope_aead, IsOk());
+
+ std::string plaintext = "plaintext";
+ std::string associated_data = "associated_data";
+ util::StatusOr<std::string> ciphertext =
+ (*envelope_aead)->Encrypt(plaintext, associated_data);
+ ASSERT_THAT(ciphertext, IsOk());
+ util::StatusOr<std::string> decrypted =
+ (*envelope_aead)->Decrypt(ciphertext.value(), associated_data);
+ EXPECT_THAT(decrypted, IsOkAndHolds(plaintext));
+}
+
+std::vector<KeyTemplate> GetTestTemplates() {
+ std::vector<KeyTemplate> templates = {
+ AeadKeyTemplates::Aes128Gcm(),
+ AeadKeyTemplates::Aes256Gcm(),
+ AeadKeyTemplates::Aes128CtrHmacSha256(),
+ AeadKeyTemplates::Aes128Eax(),
+ AeadKeyTemplates::Aes128GcmNoPrefix()
+ };
+ if (internal::IsBoringSsl()) {
+ templates.push_back(AeadKeyTemplates::XChaCha20Poly1305());
+ templates.push_back(AeadKeyTemplates::Aes256GcmSiv());
+ }
+ return templates;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ KmsEnvelopeAeadDekTemplatesTest, KmsEnvelopeAeadDekTemplatesTest,
+ testing::ValuesIn(GetTestTemplates()));
+
} // namespace
} // namespace tink
} // namespace crypto
diff --git a/cc/binary_keyset_reader.h b/cc/binary_keyset_reader.h
index 01d4220..7b718c1 100644
--- a/cc/binary_keyset_reader.h
+++ b/cc/binary_keyset_reader.h
@@ -18,6 +18,7 @@
#define TINK_BINARY_KEYSET_READER_H_
#include <istream>
+#include <memory>
#include <string>
#include "absl/strings/string_view.h"
diff --git a/cc/binary_keyset_writer.h b/cc/binary_keyset_writer.h
index 802ba41..5e5a0dc 100644
--- a/cc/binary_keyset_writer.h
+++ b/cc/binary_keyset_writer.h
@@ -17,6 +17,7 @@
#ifndef TINK_BINARY_KEYSET_WRITER_H_
#define TINK_BINARY_KEYSET_WRITER_H_
+#include <memory>
#include <ostream>
#include <utility>
@@ -38,7 +39,7 @@
std::unique_ptr<std::ostream> destination_stream);
crypto::tink::util::Status
- Write(const google::crypto::tink::Keyset& keyset) override;;
+ Write(const google::crypto::tink::Keyset& keyset) override;
crypto::tink::util::Status
Write(const google::crypto::tink::EncryptedKeyset& encrypted_keyset) override;
diff --git a/cc/catalogue.h b/cc/catalogue.h
deleted file mode 100644
index 0452c4c..0000000
--- a/cc/catalogue.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_CATALOGUE_H_
-#define TINK_CATALOGUE_H_
-
-#include <string>
-
-#include "absl/base/macros.h"
-#include "tink/key_manager.h"
-#include "tink/util/statusor.h"
-
-namespace crypto {
-namespace tink {
-
-// This class is deprecated. We don't support catalogues anymore.
-template <class P>
-class ABSL_DEPRECATED("Catalogues are not supported anymore.") Catalogue {
- public:
- // Returns a key manager for the given 'type_url', 'primitive_name',
- // and version at least 'min_version' (if any found).
- // Caller owns the returned manager.
- virtual crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<P>>>
- GetKeyManager(const std::string& type_url, const std::string& primitive_name,
- uint32_t min_version) const = 0;
-
- virtual ~Catalogue() {}
-};
-
-} // namespace tink
-} // namespace crypto
-
-#endif // TINK_CATALOGUE_H_
diff --git a/cc/config.h b/cc/config.h
deleted file mode 100644
index 099a448..0000000
--- a/cc/config.h
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_CONFIG_H_
-#define TINK_CONFIG_H_
-
-#include <string>
-
-#include "absl/status/status.h"
-#include "absl/strings/ascii.h"
-#include "tink/aead/aead_config.h"
-#include "tink/catalogue.h"
-#include "tink/daead/deterministic_aead_config.h"
-#include "tink/hybrid/hybrid_config.h"
-#include "tink/key_manager.h"
-#include "tink/mac/mac_config.h"
-#include "tink/registry.h"
-#include "tink/signature/signature_config.h"
-#include "tink/streamingaead/streaming_aead_config.h"
-#include "tink/util/errors.h"
-#include "tink/util/status.h"
-#include "proto/config.pb.h"
-
-namespace crypto {
-namespace tink {
-
-// Static methods for handling of Tink configurations.
-//
-// Configurations, i.e., collections of key types and their corresponding key
-// managers supported by a specific run-time environment enable control
-// of Tink setup via JSON-formatted config files that determine which key types
-// are supported, and provide a mechanism for deprecation of obsolete/outdated
-// cryptographic schemes (see tink/proto/config.proto for more info).
-//
-// Example usage:
-//
-// RegistryConfig registry_config = ...;
-// auto status = Config::Register(registry_config);
-//
-class Config {
- public:
- // Returns a KeyTypeEntry for Tink key types with the specified parameters.
- static std::unique_ptr<google::crypto::tink::KeyTypeEntry>
- GetTinkKeyTypeEntry(const std::string& catalogue_name,
- const std::string& primitive_name,
- const std::string& key_proto_name,
- int key_manager_version, bool new_key_allowed);
-
- // Registers a key manager according to the specification in 'entry'.
- template <class P>
- static crypto::tink::util::Status Register(
- const google::crypto::tink::KeyTypeEntry& entry);
-
- // Registers key managers and primitive wrappers according to the
- // specification in 'config'.
- static crypto::tink::util::Status Register(
- const google::crypto::tink::RegistryConfig& config);
-
- private:
- static crypto::tink::util::Status Validate(
- const google::crypto::tink::KeyTypeEntry& entry);
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// Implementation details of templated methods.
-
-// static
-template <class P>
-crypto::tink::util::Status Config::Register(
- const google::crypto::tink::KeyTypeEntry& entry) {
- util::Status status;
- std::string primitive_name = absl::AsciiStrToLower(entry.primitive_name());
-
- if (primitive_name == "mac") {
- status = MacConfig::Register();
- } else if (primitive_name == "aead") {
- status = AeadConfig::Register();
- } else if (primitive_name == "deterministicaead") {
- status = DeterministicAeadConfig::Register();
- } else if (primitive_name == "hybridencrypt" ||
- primitive_name == "hybriddecrypt") {
- status = HybridConfig::Register();
- } else if (primitive_name == "publickeysign" ||
- primitive_name == "publickeyverify") {
- status = SignatureConfig::Register();
- } else if (primitive_name == "streamingaead") {
- status = StreamingAeadConfig::Register();
- } else {
- status = util::Status(
- absl::StatusCode::kInvalidArgument,
- absl::StrCat("Non-standard primitive '", entry.primitive_name(),
- "', call Registry::RegisterKeyManager "
- "and Registry::"
- "RegisterPrimitiveWrapper directly."));
- }
- if (!status.ok()) return status;
- return util::OkStatus();
-}
-
-} // namespace tink
-} // namespace crypto
-
-#endif // TINK_CONFIG_H_
diff --git a/cc/config/BUILD.bazel b/cc/config/BUILD.bazel
index 8ec12df..8d77a3e 100644
--- a/cc/config/BUILD.bazel
+++ b/cc/config/BUILD.bazel
@@ -11,7 +11,6 @@
include_prefix = "tink/config",
visibility = ["//visibility:public"],
deps = [
- "//:config",
"//:key_manager",
"//:registry",
"//daead:deterministic_aead_config",
@@ -38,28 +37,6 @@
build_setting_default = False,
)
-bool_flag(
- name = "tink_use_absl_status",
- build_setting_default = False,
-)
-
-config_setting(
- name = "absl_status_enabled",
- flag_values = {"//config:tink_use_absl_status": "True"},
- visibility = ["//visibility:public"],
-)
-
-bool_flag(
- name = "tink_use_absl_statusor",
- build_setting_default = False,
-)
-
-config_setting(
- name = "absl_statusor_enabled",
- flag_values = {"//config:tink_use_absl_statusor": "True"},
- visibility = ["//visibility:public"],
-)
-
cc_library(
name = "tink_fips",
srcs = ["tink_fips.cc"],
@@ -75,6 +52,60 @@
],
)
+cc_library(
+ name = "fips_140_2",
+ srcs = ["fips_140_2.cc"],
+ hdrs = ["fips_140_2.h"],
+ include_prefix = "tink/config",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//:configuration",
+ "//aead:aead_wrapper",
+ "//aead:aes_ctr_hmac_aead_key_manager",
+ "//aead:aes_gcm_key_manager",
+ "//internal:configuration_impl",
+ "//internal:fips_utils",
+ "//mac:hmac_key_manager",
+ "//mac:mac_wrapper",
+ "//mac/internal:chunked_mac_wrapper",
+ "//prf:hmac_prf_key_manager",
+ "//prf:prf_set_wrapper",
+ "//signature:ecdsa_sign_key_manager",
+ "//signature:ecdsa_verify_key_manager",
+ "//signature:public_key_sign_wrapper",
+ "//signature:public_key_verify_wrapper",
+ "//signature:rsa_ssa_pkcs1_sign_key_manager",
+ "//signature:rsa_ssa_pkcs1_verify_key_manager",
+ "//signature:rsa_ssa_pss_sign_key_manager",
+ "//signature:rsa_ssa_pss_verify_key_manager",
+ "@com_google_absl//absl/log:check",
+ ],
+)
+
+cc_library(
+ name = "key_gen_fips_140_2",
+ srcs = ["key_gen_fips_140_2.cc"],
+ hdrs = ["key_gen_fips_140_2.h"],
+ include_prefix = "tink/config",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//:key_gen_configuration",
+ "//aead:aes_ctr_hmac_aead_key_manager",
+ "//aead:aes_gcm_key_manager",
+ "//internal:fips_utils",
+ "//internal:key_gen_configuration_impl",
+ "//mac:hmac_key_manager",
+ "//prf:hmac_prf_key_manager",
+ "//signature:ecdsa_sign_key_manager",
+ "//signature:ecdsa_verify_key_manager",
+ "//signature:rsa_ssa_pkcs1_sign_key_manager",
+ "//signature:rsa_ssa_pkcs1_verify_key_manager",
+ "//signature:rsa_ssa_pss_sign_key_manager",
+ "//signature:rsa_ssa_pss_verify_key_manager",
+ "@com_google_absl//absl/log:check",
+ ],
+)
+
# tests
cc_test(
@@ -84,7 +115,6 @@
deps = [
":tink_config",
"//:aead",
- "//:config",
"//:deterministic_aead",
"//:hybrid_decrypt",
"//:hybrid_encrypt",
@@ -127,3 +157,50 @@
"@com_google_googletest//:gtest_main",
],
)
+
+cc_test(
+ name = "fips_140_2_test",
+ srcs = ["fips_140_2_test.cc"],
+ deps = [
+ ":fips_140_2",
+ "//aead:aead_key_templates",
+ "//aead:aes_ctr_hmac_aead_key_manager",
+ "//aead:aes_gcm_key_manager",
+ "//internal:configuration_impl",
+ "//internal:fips_utils",
+ "//internal:key_type_info_store",
+ "//mac:aes_cmac_key_manager",
+ "//mac:hmac_key_manager",
+ "//prf:hmac_prf_key_manager",
+ "//proto:tink_cc_proto",
+ "//signature:ecdsa_verify_key_manager",
+ "//signature:rsa_ssa_pkcs1_verify_key_manager",
+ "//signature:rsa_ssa_pss_verify_key_manager",
+ "//util:test_keyset_handle",
+ "//util:test_matchers",
+ "//util:test_util",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "key_gen_fips_140_2_test",
+ srcs = ["key_gen_fips_140_2_test.cc"],
+ deps = [
+ ":key_gen_fips_140_2",
+ "//aead:aead_key_templates",
+ "//aead:aes_ctr_hmac_aead_key_manager",
+ "//aead:aes_gcm_key_manager",
+ "//internal:fips_utils",
+ "//internal:key_gen_configuration_impl",
+ "//mac:aes_cmac_key_manager",
+ "//mac:hmac_key_manager",
+ "//prf:hmac_prf_key_manager",
+ "//proto:tink_cc_proto",
+ "//signature:ecdsa_verify_key_manager",
+ "//signature:rsa_ssa_pkcs1_verify_key_manager",
+ "//signature:rsa_ssa_pss_verify_key_manager",
+ "//util:test_matchers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/cc/config/CMakeLists.txt b/cc/config/CMakeLists.txt
index d479a98..010958c 100644
--- a/cc/config/CMakeLists.txt
+++ b/cc/config/CMakeLists.txt
@@ -1,5 +1,7 @@
tink_module(config)
+add_subdirectory(internal)
+
tink_cc_library(
NAME tink_config
SRCS
@@ -7,7 +9,6 @@
tink_config.h
DEPS
absl::core_headers
- tink::core::config
tink::core::key_manager
tink::core::registry
tink::daead::deterministic_aead_config
@@ -41,6 +42,56 @@
tink::util::status
)
+tink_cc_library(
+ NAME fips_140_2
+ SRCS
+ fips_140_2.cc
+ fips_140_2.h
+ DEPS
+ absl::check
+ tink::core::configuration
+ tink::aead::aead_wrapper
+ tink::aead::aes_ctr_hmac_aead_key_manager
+ tink::aead::aes_gcm_key_manager
+ tink::internal::configuration_impl
+ tink::internal::fips_utils
+ tink::mac::hmac_key_manager
+ tink::mac::mac_wrapper
+ tink::mac::internal::chunked_mac_wrapper
+ tink::prf::hmac_prf_key_manager
+ tink::prf::prf_set_wrapper
+ tink::signature::ecdsa_verify_key_manager
+ tink::signature::public_key_sign_wrapper
+ tink::signature::public_key_verify_wrapper
+ tink::signature::rsa_ssa_pkcs1_sign_key_manager
+ tink::signature::rsa_ssa_pkcs1_verify_key_manager
+ tink::signature::rsa_ssa_pss_sign_key_manager
+ tink::signature::rsa_ssa_pss_verify_key_manager
+ tink::signature::ecdsa_sign_key_manager
+)
+
+tink_cc_library(
+ NAME key_gen_fips_140_2
+ SRCS
+ key_gen_fips_140_2.cc
+ key_gen_fips_140_2.h
+ DEPS
+ absl::check
+ tink::core::key_gen_configuration
+ tink::aead::aes_ctr_hmac_aead_key_manager
+ tink::aead::aes_gcm_key_manager
+ tink::internal::fips_utils
+ tink::internal::key_gen_configuration_impl
+ tink::mac::hmac_key_manager
+ tink::prf::hmac_prf_key_manager
+ tink::signature::ecdsa_verify_key_manager
+ tink::signature::rsa_ssa_pkcs1_sign_key_manager
+ tink::signature::rsa_ssa_pkcs1_verify_key_manager
+ tink::signature::rsa_ssa_pss_sign_key_manager
+ tink::signature::rsa_ssa_pss_verify_key_manager
+ tink::signature::ecdsa_sign_key_manager
+)
+
# tests
tink_cc_test(
@@ -53,7 +104,6 @@
absl::status
tink::core::cc
tink::core::aead
- tink::core::config
tink::core::deterministic_aead
tink::core::hybrid_decrypt
tink::core::hybrid_encrypt
@@ -89,3 +139,50 @@
tink::util::status
tink::util::test_matchers
)
+
+tink_cc_test(
+ NAME fips_140_2_test
+ SRCS
+ fips_140_2_test.cc
+ DEPS
+ tink::config::fips_140_2
+ gmock
+ tink::aead::aead_key_templates
+ tink::aead::aes_ctr_hmac_aead_key_manager
+ tink::aead::aes_gcm_key_manager
+ tink::internal::configuration_impl
+ tink::internal::fips_utils
+ tink::internal::key_type_info_store
+ tink::mac::aes_cmac_key_manager
+ tink::mac::hmac_key_manager
+ tink::prf::hmac_prf_key_manager
+ tink::signature::ecdsa_verify_key_manager
+ tink::signature::rsa_ssa_pkcs1_verify_key_manager
+ tink::signature::rsa_ssa_pss_verify_key_manager
+ tink::util::test_keyset_handle
+ tink::util::test_matchers
+ tink::util::test_util
+ tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+ NAME key_gen_fips_140_2_test
+ SRCS
+ key_gen_fips_140_2_test.cc
+ DEPS
+ tink::config::key_gen_fips_140_2
+ gmock
+ tink::aead::aead_key_templates
+ tink::aead::aes_ctr_hmac_aead_key_manager
+ tink::aead::aes_gcm_key_manager
+ tink::internal::fips_utils
+ tink::internal::key_gen_configuration_impl
+ tink::mac::aes_cmac_key_manager
+ tink::mac::hmac_key_manager
+ tink::prf::hmac_prf_key_manager
+ tink::signature::ecdsa_verify_key_manager
+ tink::signature::rsa_ssa_pkcs1_verify_key_manager
+ tink::signature::rsa_ssa_pss_verify_key_manager
+ tink::util::test_matchers
+ tink::proto::tink_cc_proto
+)
diff --git a/cc/config/fips_140_2.cc b/cc/config/fips_140_2.cc
new file mode 100644
index 0000000..0eafff5
--- /dev/null
+++ b/cc/config/fips_140_2.cc
@@ -0,0 +1,134 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/config/fips_140_2.h"
+
+#include "absl/log/check.h"
+#include "tink/aead/aead_wrapper.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/configuration.h"
+#include "tink/internal/configuration_impl.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/mac/hmac_key_manager.h"
+#include "tink/mac/internal/chunked_mac_wrapper.h"
+#include "tink/mac/mac_wrapper.h"
+#include "tink/prf/hmac_prf_key_manager.h"
+#include "tink/prf/prf_set_wrapper.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/public_key_sign_wrapper.h"
+#include "tink/signature/public_key_verify_wrapper.h"
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/signature/ecdsa_sign_key_manager.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::Status AddMac(Configuration& config) {
+ util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+ absl::make_unique<MacWrapper>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+ status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+ absl::make_unique<internal::ChunkedMacWrapper>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+
+ return internal::ConfigurationImpl::AddKeyTypeManager(
+ absl::make_unique<HmacKeyManager>(), config);
+}
+
+util::Status AddAead(Configuration& config) {
+ util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+ absl::make_unique<AeadWrapper>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+
+ status = internal::ConfigurationImpl::AddKeyTypeManager(
+ absl::make_unique<AesCtrHmacAeadKeyManager>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+ return internal::ConfigurationImpl::AddKeyTypeManager(
+ absl::make_unique<AesGcmKeyManager>(), config);
+}
+
+util::Status AddPrf(Configuration& config) {
+ util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+ absl::make_unique<PrfSetWrapper>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+
+ return internal::ConfigurationImpl::AddKeyTypeManager(
+ absl::make_unique<HmacPrfKeyManager>(), config);
+}
+
+util::Status AddSignature(Configuration& config) {
+ util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+ absl::make_unique<PublicKeySignWrapper>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+ status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+ absl::make_unique<PublicKeyVerifyWrapper>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+
+ status = internal::ConfigurationImpl::AddAsymmetricKeyManagers(
+ absl::make_unique<EcdsaSignKeyManager>(),
+ absl::make_unique<EcdsaVerifyKeyManager>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+ status = internal::ConfigurationImpl::AddAsymmetricKeyManagers(
+ absl::make_unique<RsaSsaPssSignKeyManager>(),
+ absl::make_unique<RsaSsaPssVerifyKeyManager>(), config);
+ if (!status.ok()) {
+ return status;
+ }
+ return internal::ConfigurationImpl::AddAsymmetricKeyManagers(
+ absl::make_unique<RsaSsaPkcs1SignKeyManager>(),
+ absl::make_unique<RsaSsaPkcs1VerifyKeyManager>(), config);
+}
+
+} // namespace
+
+const Configuration& ConfigFips140_2() {
+ static const Configuration* instance = [] {
+ internal::SetFipsRestricted();
+
+ static Configuration* config = new Configuration();
+ CHECK_OK(AddMac(*config));
+ CHECK_OK(AddAead(*config));
+ CHECK_OK(AddPrf(*config));
+ CHECK_OK(AddSignature(*config));
+
+ return config;
+ }();
+ return *instance;
+}
+
+} // namespace tink
+} // namespace crypto
diff --git a/cc/config/fips_140_2.h b/cc/config/fips_140_2.h
new file mode 100644
index 0000000..24fa9b1
--- /dev/null
+++ b/cc/config/fips_140_2.h
@@ -0,0 +1,33 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_CONFIG_FIPS_140_2_H_
+#define TINK_CONFIG_FIPS_140_2_H_
+
+#include "tink/configuration.h"
+
+namespace crypto {
+namespace tink {
+
+// Configuration used to generate primitives using FIPS 140-2-compliant key
+// types. Importing this Configuration restricts Tink to FIPS globally and
+// requires BoringSSL to be built with the BoringCrypto module.
+const Configuration& ConfigFips140_2();
+
+} // namespace tink
+} // namespace crypto
+
+#endif // TINK_CONFIG_FIPS_140_2_H_
diff --git a/cc/config/fips_140_2_test.cc b/cc/config/fips_140_2_test.cc
new file mode 100644
index 0000000..7f134c9
--- /dev/null
+++ b/cc/config/fips_140_2_test.cc
@@ -0,0 +1,142 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/config/fips_140_2.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/internal/configuration_impl.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/key_type_info_store.h"
+#include "tink/mac/aes_cmac_key_manager.h"
+#include "tink/mac/hmac_key_manager.h"
+#include "tink/prf/hmac_prf_key_manager.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/util/test_keyset_handle.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+
+class Fips1402Test : public ::testing::Test {
+ protected:
+ void TearDown() override { internal::UnSetFipsRestricted(); }
+};
+
+TEST_F(Fips1402Test, ConfigFips1402) {
+ if (!internal::IsFipsEnabledInSsl()) {
+ GTEST_SKIP() << "Only test in FIPS mode";
+ }
+
+ util::StatusOr<const internal::KeyTypeInfoStore*> store =
+ internal::ConfigurationImpl::GetKeyTypeInfoStore(ConfigFips140_2());
+ ASSERT_THAT(store, IsOk());
+
+ EXPECT_THAT((*store)->Get(HmacKeyManager().get_key_type()), IsOk());
+ EXPECT_THAT((*store)->Get(AesCtrHmacAeadKeyManager().get_key_type()), IsOk());
+ EXPECT_THAT((*store)->Get(AesGcmKeyManager().get_key_type()), IsOk());
+ EXPECT_THAT((*store)->Get(HmacPrfKeyManager().get_key_type()), IsOk());
+ EXPECT_THAT((*store)->Get(EcdsaVerifyKeyManager().get_key_type()), IsOk());
+ EXPECT_THAT((*store)->Get(RsaSsaPssVerifyKeyManager().get_key_type()),
+ IsOk());
+ EXPECT_THAT((*store)->Get(RsaSsaPkcs1VerifyKeyManager().get_key_type()),
+ IsOk());
+}
+
+TEST_F(Fips1402Test, ConfigFips1402FailsInNonFipsMode) {
+ if (internal::IsFipsEnabledInSsl()) {
+ GTEST_SKIP() << "Only test in non-FIPS mode";
+ }
+
+ EXPECT_DEATH_IF_SUPPORTED(
+ ConfigFips140_2(), "BoringSSL not built with the BoringCrypto module.");
+}
+
+TEST_F(Fips1402Test, NonFipsTypeNotPresent) {
+ if (!internal::IsFipsEnabledInSsl()) {
+ GTEST_SKIP() << "Only test in FIPS mode";
+ }
+
+ util::StatusOr<const internal::KeyTypeInfoStore*> store =
+ internal::ConfigurationImpl::GetKeyTypeInfoStore(ConfigFips140_2());
+ ASSERT_THAT(store, IsOk());
+ EXPECT_THAT((*store)->Get(AesCmacKeyManager().get_key_type()).status(),
+ StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(Fips1402Test, NewKeyDataAndGetPrimitive) {
+ if (!internal::IsFipsEnabledInSsl()) {
+ GTEST_SKIP() << "Only test in FIPS mode";
+ }
+
+ // TODO(b/265705174): Replace with KeysetHandle::GenerateNew once that takes a
+ // config parameter.
+ util::StatusOr<const internal::KeyTypeInfoStore*> store =
+ internal::ConfigurationImpl::GetKeyTypeInfoStore(ConfigFips140_2());
+ ASSERT_THAT(store, IsOk());
+ KeyTemplate templ = AeadKeyTemplates::Aes128Gcm();
+ util::StatusOr<internal::KeyTypeInfoStore::Info*> info =
+ (*store)->Get(templ.type_url());
+ ASSERT_THAT(info, IsOk());
+
+ util::StatusOr<std::unique_ptr<KeyData>> key_data =
+ (*info)->key_factory().NewKeyData(templ.value());
+ ASSERT_THAT(key_data, IsOk());
+
+ Keyset keyset;
+ uint32_t key_id = 0;
+ test::AddKeyData(**key_data, key_id, OutputPrefixType::TINK,
+ KeyStatusType::ENABLED, &keyset);
+ keyset.set_primary_key_id(key_id);
+
+ std::unique_ptr<KeysetHandle> handle =
+ TestKeysetHandle::GetKeysetHandle(keyset);
+ util::StatusOr<std::unique_ptr<Aead>> aead =
+ handle->GetPrimitive<Aead>(ConfigFips140_2());
+ EXPECT_THAT(aead, IsOk());
+
+ std::string plaintext = "plaintext";
+ std::string ad = "ad";
+ util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(plaintext, ad);
+ ASSERT_THAT(ciphertext, IsOk());
+
+ util::StatusOr<std::string> decrypted = (*aead)->Decrypt(*ciphertext, ad);
+ EXPECT_THAT(decrypted, IsOkAndHolds(plaintext));
+}
+
+} // namespace
+} // namespace tink
+} // namespace crypto
diff --git a/cc/config/internal/BUILD.bazel b/cc/config/internal/BUILD.bazel
new file mode 100644
index 0000000..6d94c3a
--- /dev/null
+++ b/cc/config/internal/BUILD.bazel
@@ -0,0 +1,29 @@
+package(def